shopify-money 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitignore +51 -0
- data/.rspec +1 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +20 -0
- data/README.md +156 -0
- data/Rakefile +42 -0
- data/bin/console +14 -0
- data/circle.yml +13 -0
- data/config/currency_historic.json +157 -0
- data/config/currency_iso.json +2642 -0
- data/config/currency_non_iso.json +82 -0
- data/dev.yml +9 -0
- data/lib/money.rb +10 -0
- data/lib/money/accounting_money_parser.rb +8 -0
- data/lib/money/core_extensions.rb +18 -0
- data/lib/money/currency.rb +59 -0
- data/lib/money/currency/loader.rb +26 -0
- data/lib/money/deprecations.rb +18 -0
- data/lib/money/helpers.rb +71 -0
- data/lib/money/money.rb +408 -0
- data/lib/money/money_parser.rb +152 -0
- data/lib/money/null_currency.rb +35 -0
- data/lib/money/version.rb +3 -0
- data/lib/money_accessor.rb +32 -0
- data/lib/money_column.rb +3 -0
- data/lib/money_column/active_record_hooks.rb +95 -0
- data/lib/money_column/active_record_type.rb +6 -0
- data/lib/money_column/railtie.rb +7 -0
- data/money.gemspec +27 -0
- data/spec/accounting_money_parser_spec.rb +204 -0
- data/spec/core_extensions_spec.rb +44 -0
- data/spec/currency/loader_spec.rb +21 -0
- data/spec/currency_spec.rb +113 -0
- data/spec/helpers_spec.rb +103 -0
- data/spec/money_accessor_spec.rb +86 -0
- data/spec/money_column_spec.rb +298 -0
- data/spec/money_parser_spec.rb +355 -0
- data/spec/money_spec.rb +853 -0
- data/spec/null_currency_spec.rb +46 -0
- data/spec/schema.rb +9 -0
- data/spec/spec_helper.rb +74 -0
- metadata +196 -0
@@ -0,0 +1,355 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe MoneyParser do
|
4
|
+
before(:each) do
|
5
|
+
@parser = MoneyParser
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "parsing of amounts with period decimal separator" do
|
9
|
+
it "parses an empty string to $0" do
|
10
|
+
expect(@parser.parse("")).to eq(Money.zero)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "parses an invalid string when not strict" do
|
14
|
+
expect(Money).to receive(:deprecate).twice
|
15
|
+
expect(@parser.parse("no money")).to eq(Money.zero)
|
16
|
+
expect(@parser.parse("1..")).to eq(Money.new(1))
|
17
|
+
end
|
18
|
+
|
19
|
+
it "parses raise with an invalid string and strict option" do
|
20
|
+
expect { @parser.parse("no money", strict: true) }.to raise_error(MoneyParser::MoneyFormatError)
|
21
|
+
expect { @parser.parse("1..1", strict: true) }.to raise_error(MoneyParser::MoneyFormatError)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "parses a single digit integer string" do
|
25
|
+
expect(@parser.parse("1")).to eq(Money.new(1.00))
|
26
|
+
end
|
27
|
+
|
28
|
+
it "parses a double digit integer string" do
|
29
|
+
expect(@parser.parse("10")).to eq(Money.new(10.00))
|
30
|
+
end
|
31
|
+
|
32
|
+
it "parses an integer string amount with a leading $" do
|
33
|
+
expect(@parser.parse("$1")).to eq(Money.new(1.00))
|
34
|
+
end
|
35
|
+
|
36
|
+
it "parses a float string amount" do
|
37
|
+
expect(@parser.parse("1.37")).to eq(Money.new(1.37))
|
38
|
+
end
|
39
|
+
|
40
|
+
it "parses a float string amount with a leading $" do
|
41
|
+
expect(@parser.parse("$1.37")).to eq(Money.new(1.37))
|
42
|
+
end
|
43
|
+
|
44
|
+
it "parses a float string with a single digit after the decimal" do
|
45
|
+
expect(@parser.parse("10.0")).to eq(Money.new(10.00))
|
46
|
+
end
|
47
|
+
|
48
|
+
it "parses a float string with two digits after the decimal" do
|
49
|
+
expect(@parser.parse("10.00")).to eq(Money.new(10.00))
|
50
|
+
end
|
51
|
+
|
52
|
+
it "parses the amount from an amount surrounded by whitespace and garbage" do
|
53
|
+
expect(@parser.parse("Rubbish $1.00 Rubbish")).to eq(Money.new(1.00))
|
54
|
+
end
|
55
|
+
|
56
|
+
it "parses the amount from an amount surrounded by garbage" do
|
57
|
+
expect(@parser.parse("Rubbish$1.00Rubbish")).to eq(Money.new(1.00))
|
58
|
+
end
|
59
|
+
|
60
|
+
it "parses a negative integer amount in the hundreds" do
|
61
|
+
expect(@parser.parse("-100")).to eq(Money.new(-100.00))
|
62
|
+
end
|
63
|
+
|
64
|
+
it "parses an integer amount in the hundreds" do
|
65
|
+
expect(@parser.parse("410")).to eq(Money.new(410.00))
|
66
|
+
end
|
67
|
+
|
68
|
+
it "parses an amount ending with a ." do
|
69
|
+
expect(@parser.parse("1.")).to eq(Money.new(1))
|
70
|
+
expect(@parser.parse("100,000.")).to eq(Money.new(100_000))
|
71
|
+
end
|
72
|
+
|
73
|
+
it "parses an amount starting with a ." do
|
74
|
+
expect(@parser.parse(".12")).to eq(Money.new(0.12))
|
75
|
+
end
|
76
|
+
|
77
|
+
it "parses a positive amount with a thousands separator" do
|
78
|
+
expect(@parser.parse("100,000.00")).to eq(Money.new(100_000.00))
|
79
|
+
end
|
80
|
+
|
81
|
+
it "parses a negative amount with a thousands separator" do
|
82
|
+
expect(@parser.parse("-100,000.00")).to eq(Money.new(-100_000.00))
|
83
|
+
end
|
84
|
+
|
85
|
+
it "parses a positive amount with a thousands separator with no decimal" do
|
86
|
+
expect(@parser.parse("1,000")).to eq(Money.new(1_000))
|
87
|
+
end
|
88
|
+
|
89
|
+
it "parses a positive amount with a thousands separator with no decimal with a currency" do
|
90
|
+
expect(@parser.parse("1,000", 'JOD')).to eq(Money.new(1_000, 'JOD'))
|
91
|
+
end
|
92
|
+
|
93
|
+
it "parses a positive amount with a thousands separator with no decimal" do
|
94
|
+
expect(@parser.parse("12,34,567.89", 'INR')).to eq(Money.new(1_234_567.89, 'INR'))
|
95
|
+
end
|
96
|
+
|
97
|
+
it "parses negative $1.00" do
|
98
|
+
expect(@parser.parse("-1.00")).to eq(Money.new(-1.00))
|
99
|
+
end
|
100
|
+
|
101
|
+
it "parses a negative cents amount" do
|
102
|
+
expect(@parser.parse("-0.90")).to eq(Money.new(-0.90))
|
103
|
+
end
|
104
|
+
|
105
|
+
it "parses amount with 3 decimals and 0 dollar amount" do
|
106
|
+
expect(@parser.parse("0.123")).to eq(Money.new(0.12))
|
107
|
+
end
|
108
|
+
|
109
|
+
it "parses negative amount with 3 decimals and 0 dollar amount" do
|
110
|
+
expect(@parser.parse("-0.123")).to eq(Money.new(-0.12))
|
111
|
+
end
|
112
|
+
|
113
|
+
it "parses negative amount with multiple leading - signs" do
|
114
|
+
expect(@parser.parse("--0.123")).to eq(Money.new(-0.12))
|
115
|
+
end
|
116
|
+
|
117
|
+
it "parses negative amount with multiple - signs" do
|
118
|
+
expect(@parser.parse("--0.123--")).to eq(Money.new(-0.12))
|
119
|
+
end
|
120
|
+
|
121
|
+
it "parses a positive amount with a thousands dot separator currency and no decimal" do
|
122
|
+
expect(@parser.parse("1.000", 'EUR')).to eq(Money.new(1_000, 'EUR'))
|
123
|
+
end
|
124
|
+
|
125
|
+
it "parses a three digit currency" do
|
126
|
+
expect(@parser.parse("1.000", 'JOD')).to eq(Money.new(1, 'JOD'))
|
127
|
+
end
|
128
|
+
|
129
|
+
it "parses uses currency when passed as block to with_currency" do
|
130
|
+
expect(Money.with_currency('JOD') { @parser.parse("1.000") }).to eq(Money.new(1, 'JOD'))
|
131
|
+
end
|
132
|
+
|
133
|
+
it "parses no currency amount" do
|
134
|
+
expect(@parser.parse("1.000", Money::NULL_CURRENCY)).to eq(Money.new(1000, Money::NULL_CURRENCY))
|
135
|
+
end
|
136
|
+
|
137
|
+
it "parses amount with more than 3 decimals correctly" do
|
138
|
+
expect(@parser.parse("1.11111111")).to eq(Money.new(1.11))
|
139
|
+
end
|
140
|
+
|
141
|
+
it "parses amount with multiple consistent thousands delimiters" do
|
142
|
+
expect(@parser.parse("1.111.111")).to eq(Money.new(1_111_111))
|
143
|
+
end
|
144
|
+
|
145
|
+
it "parses amount with multiple inconsistent thousands delimiters" do
|
146
|
+
expect(Money).to receive(:deprecate).once
|
147
|
+
expect(@parser.parse("1.1.11.111")).to eq(Money.new(1_111_111))
|
148
|
+
end
|
149
|
+
|
150
|
+
it "parses raises with multiple inconsistent thousands delimiters and strict option" do
|
151
|
+
expect { @parser.parse("1.1.11.111", strict: true) }.to raise_error(MoneyParser::MoneyFormatError)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "parsing of amounts with comma decimal separator" do
|
156
|
+
it "parses dollar amount $1,00 with leading $" do
|
157
|
+
expect(@parser.parse("$1,00")).to eq(Money.new(1.00))
|
158
|
+
end
|
159
|
+
|
160
|
+
it "parses dollar amount $1,37 with leading $, and non-zero cents" do
|
161
|
+
expect(@parser.parse("$1,37")).to eq(Money.new(1.37))
|
162
|
+
end
|
163
|
+
|
164
|
+
it "parses the amount from an amount surrounded by whitespace and garbage" do
|
165
|
+
expect(@parser.parse("Rubbish $1,00 Rubbish")).to eq(Money.new(1.00))
|
166
|
+
end
|
167
|
+
|
168
|
+
it "parses the amount from an amount surrounded by garbage" do
|
169
|
+
expect(@parser.parse("Rubbish$1,00Rubbish")).to eq(Money.new(1.00))
|
170
|
+
end
|
171
|
+
|
172
|
+
it "parses negative hundreds amount" do
|
173
|
+
expect(@parser.parse("-100,00")).to eq(Money.new(-100.00))
|
174
|
+
end
|
175
|
+
|
176
|
+
it "parses positive hundreds amount" do
|
177
|
+
expect(@parser.parse("410,00")).to eq(Money.new(410.00))
|
178
|
+
end
|
179
|
+
|
180
|
+
it "parses a positive amount with a thousands separator" do
|
181
|
+
expect(@parser.parse("100.000,00")).to eq(Money.new(100_000.00))
|
182
|
+
end
|
183
|
+
|
184
|
+
it "parses a negative amount with a thousands separator" do
|
185
|
+
expect(@parser.parse("-100.000,00")).to eq(Money.new(-100_000.00))
|
186
|
+
end
|
187
|
+
|
188
|
+
it "parses amount ending with a comma" do
|
189
|
+
expect(@parser.parse("1,")).to eq(Money.new(1))
|
190
|
+
expect(@parser.parse("100.000,")).to eq(Money.new(100_000))
|
191
|
+
end
|
192
|
+
|
193
|
+
it "parses amount with 3 decimals and 0 dollar amount" do
|
194
|
+
expect(@parser.parse("0,123")).to eq(Money.new(0.12))
|
195
|
+
end
|
196
|
+
|
197
|
+
it "parses negative amount with 3 decimals and 0 dollar amount" do
|
198
|
+
expect(@parser.parse("-0,123")).to eq(Money.new(-0.12))
|
199
|
+
end
|
200
|
+
|
201
|
+
it "parses amount 2 decimals correctly" do
|
202
|
+
expect(@parser.parse("1,11", Money::NULL_CURRENCY)).to eq(Money.new(1.11, Money::NULL_CURRENCY))
|
203
|
+
end
|
204
|
+
|
205
|
+
it "parses amount with more than 3 decimals correctly" do
|
206
|
+
expect(@parser.parse("1,11111111", Money::NULL_CURRENCY)).to eq(Money.new(111_111_111, Money::NULL_CURRENCY))
|
207
|
+
end
|
208
|
+
|
209
|
+
it "parses amount with more than 3 decimals correctly and a currency" do
|
210
|
+
expect(@parser.parse("1,11111111", 'CAD')).to eq(Money.new(111_111_111))
|
211
|
+
end
|
212
|
+
|
213
|
+
it "parses amount with multiple consistent thousands delimiters" do
|
214
|
+
expect(@parser.parse("1,111,111")).to eq(Money.new(1_111_111))
|
215
|
+
end
|
216
|
+
|
217
|
+
it "parses amount with multiple inconsistent thousands delimiters" do
|
218
|
+
expect(Money).to receive(:deprecate).once
|
219
|
+
expect(@parser.parse("1,1,11,111")).to eq(Money.new(1_111_111))
|
220
|
+
end
|
221
|
+
|
222
|
+
it "parses raises with multiple inconsistent thousands delimiters and strict option" do
|
223
|
+
expect { @parser.parse("1,1,11,111", strict: true) }.to raise_error(MoneyParser::MoneyFormatError)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe "3 digit decimal currency" do
|
228
|
+
it "parses thousands correctly" do
|
229
|
+
expect(@parser.parse("1,111", "JOD")).to eq(Money.new(1_111, 'JOD'))
|
230
|
+
expect(@parser.parse("1.111.111", "JOD")).to eq(Money.new(1_111_111, 'JOD'))
|
231
|
+
expect(@parser.parse("1 111", "JOD")).to eq(Money.new(1_111, 'JOD'))
|
232
|
+
expect(@parser.parse("1111,111", "JOD")).to eq(Money.new(1_111_111, 'JOD'))
|
233
|
+
end
|
234
|
+
|
235
|
+
it "parses decimals correctly" do
|
236
|
+
expect(@parser.parse("1.111", "JOD")).to eq(Money.new(1.111, 'JOD'))
|
237
|
+
expect(@parser.parse("1,11", "JOD")).to eq(Money.new(1.110, 'JOD'))
|
238
|
+
expect(@parser.parse("1111.111", "JOD")).to eq(Money.new(1_111.111, 'JOD'))
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
describe "no decimal currency" do
|
243
|
+
it "parses thousands correctly" do
|
244
|
+
expect(@parser.parse("1,111", "JPY")).to eq(Money.new(1_111, 'JPY'))
|
245
|
+
expect(@parser.parse("1.111", "JPY")).to eq(Money.new(1_111, 'JPY'))
|
246
|
+
expect(@parser.parse("1 111", "JPY")).to eq(Money.new(1_111, 'JPY'))
|
247
|
+
expect(@parser.parse("1111,111", "JPY")).to eq(Money.new(1_111_111, 'JPY'))
|
248
|
+
end
|
249
|
+
|
250
|
+
it "parses decimals correctly" do
|
251
|
+
expect(@parser.parse("1,11", "JPY")).to eq(Money.new(1, 'JPY'))
|
252
|
+
expect(@parser.parse("1.11", "JPY")).to eq(Money.new(1, 'JPY'))
|
253
|
+
expect(@parser.parse("1111.111", "JPY")).to eq(Money.new(1111, 'JPY'))
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
describe "two decimal currency" do
|
258
|
+
it "parses thousands correctly" do
|
259
|
+
expect(@parser.parse("1,111", "USD")).to eq(Money.new(1_111, 'USD'))
|
260
|
+
expect(@parser.parse("1.111", "USD")).to eq(Money.new(1_111, 'USD'))
|
261
|
+
expect(@parser.parse("1 111", "USD")).to eq(Money.new(1_111, 'USD'))
|
262
|
+
expect(@parser.parse("1111,111", "USD")).to eq(Money.new(1_111_111, 'USD'))
|
263
|
+
end
|
264
|
+
|
265
|
+
it "parses decimals correctly" do
|
266
|
+
expect(@parser.parse("1,11", "USD")).to eq(Money.new(1.11, 'USD'))
|
267
|
+
expect(@parser.parse("1.11", "USD")).to eq(Money.new(1.11, 'USD'))
|
268
|
+
expect(@parser.parse("1111.111", "USD")).to eq(Money.new(1111.11, 'USD'))
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe "parsing of decimal cents amounts from 0 to 10" do
|
273
|
+
it "parses 50.0" do
|
274
|
+
expect(@parser.parse("50.0")).to eq(Money.new(50.00))
|
275
|
+
end
|
276
|
+
|
277
|
+
it "parses 50.1" do
|
278
|
+
expect(@parser.parse("50.1")).to eq(Money.new(50.10))
|
279
|
+
end
|
280
|
+
|
281
|
+
it "parses 50.2" do
|
282
|
+
expect(@parser.parse("50.2")).to eq(Money.new(50.20))
|
283
|
+
end
|
284
|
+
|
285
|
+
it "parses 50.3" do
|
286
|
+
expect(@parser.parse("50.3")).to eq(Money.new(50.30))
|
287
|
+
end
|
288
|
+
|
289
|
+
it "parses 50.4" do
|
290
|
+
expect(@parser.parse("50.4")).to eq(Money.new(50.40))
|
291
|
+
end
|
292
|
+
|
293
|
+
it "parses 50.5" do
|
294
|
+
expect(@parser.parse("50.5")).to eq(Money.new(50.50))
|
295
|
+
end
|
296
|
+
|
297
|
+
it "parses 50.6" do
|
298
|
+
expect(@parser.parse("50.6")).to eq(Money.new(50.60))
|
299
|
+
end
|
300
|
+
|
301
|
+
it "parses 50.7" do
|
302
|
+
expect(@parser.parse("50.7")).to eq(Money.new(50.70))
|
303
|
+
end
|
304
|
+
|
305
|
+
it "parses 50.8" do
|
306
|
+
expect(@parser.parse("50.8")).to eq(Money.new(50.80))
|
307
|
+
end
|
308
|
+
|
309
|
+
it "parses 50.9" do
|
310
|
+
expect(@parser.parse("50.9")).to eq(Money.new(50.90))
|
311
|
+
end
|
312
|
+
|
313
|
+
it "parses 50.10" do
|
314
|
+
expect(@parser.parse("50.10")).to eq(Money.new(50.10))
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
describe "parsing of fixnum" do
|
319
|
+
it "parses 1" do
|
320
|
+
expect(@parser.parse(1)).to eq(Money.new(1))
|
321
|
+
end
|
322
|
+
|
323
|
+
it "parses 50" do
|
324
|
+
expect(@parser.parse(50)).to eq(Money.new(50))
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
describe "parsing of float" do
|
329
|
+
it "parses 1.00" do
|
330
|
+
expect(@parser.parse(1.00)).to eq(Money.new(1.00))
|
331
|
+
end
|
332
|
+
|
333
|
+
it "parses 1.32" do
|
334
|
+
expect(@parser.parse(1.32)).to eq(Money.new(1.32))
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
describe "parsing with thousands separators" do
|
339
|
+
[
|
340
|
+
'1,234,567.89',
|
341
|
+
'1 234 567.89',
|
342
|
+
'1 234 567,89',
|
343
|
+
'1.234.567,89',
|
344
|
+
'1˙234˙567,89',
|
345
|
+
'12,34,567.89',
|
346
|
+
"1'234'567.89",
|
347
|
+
"1'234'567,89",
|
348
|
+
'123,4567.89',
|
349
|
+
].each do |number|
|
350
|
+
it "parses #{number}" do
|
351
|
+
expect(@parser.parse(number)).to eq(Money.new(1_234_567.89))
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
data/spec/money_spec.rb
ADDED
@@ -0,0 +1,853 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
RSpec.describe "Money" do
|
5
|
+
|
6
|
+
let (:money) {Money.new(1)}
|
7
|
+
let (:amount_money) { Money.new(1.23, 'USD') }
|
8
|
+
let (:non_fractional_money) { Money.new(1, 'JPY') }
|
9
|
+
let (:zero_money) { Money.new(0) }
|
10
|
+
|
11
|
+
it "is contructable with empty class method" do
|
12
|
+
expect(Money.empty).to eq(Money.new)
|
13
|
+
end
|
14
|
+
|
15
|
+
context "default currency not set" do
|
16
|
+
before(:each) do
|
17
|
+
@default_currency = Money.default_currency
|
18
|
+
Money.default_currency = nil
|
19
|
+
end
|
20
|
+
after(:each) do
|
21
|
+
Money.default_currency = @default_currency
|
22
|
+
end
|
23
|
+
|
24
|
+
it "raises an error" do
|
25
|
+
expect { money }.to raise_error(ArgumentError)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it ".zero has no currency" do
|
30
|
+
expect(Money.zero.currency).to be_a(Money::NullCurrency)
|
31
|
+
end
|
32
|
+
|
33
|
+
it ".zero is a 0$ value" do
|
34
|
+
expect(Money.zero).to eq(Money.new(0))
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns itself with to_money" do
|
38
|
+
expect(money.to_money).to eq(money)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "defaults to 0 when constructed with no arguments" do
|
42
|
+
expect(Money.new).to eq(Money.new(0))
|
43
|
+
end
|
44
|
+
|
45
|
+
it "defaults to 0 when constructed with an empty string" do
|
46
|
+
expect(Money.new('')).to eq(Money.new(0))
|
47
|
+
end
|
48
|
+
|
49
|
+
it "defaults to 0 when constructed with an invalid string" do
|
50
|
+
expect(Money).to receive(:deprecate).once
|
51
|
+
expect(Money.new('invalid')).to eq(Money.new(0.00))
|
52
|
+
end
|
53
|
+
|
54
|
+
it "to_s correctly displays the right number of decimal places" do
|
55
|
+
expect(money.to_s).to eq("1.00")
|
56
|
+
expect(non_fractional_money.to_s).to eq("1")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "to_s with a legacy_dollars style" do
|
60
|
+
expect(amount_money.to_s(:legacy_dollars)).to eq("1.23")
|
61
|
+
expect(non_fractional_money.to_s(:legacy_dollars)).to eq("1.00")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "to_s with a amount style" do
|
65
|
+
expect(amount_money.to_s(:amount)).to eq("1.23")
|
66
|
+
expect(non_fractional_money.to_s(:amount)).to eq("1")
|
67
|
+
end
|
68
|
+
|
69
|
+
it "as_json as a float with 2 decimal places" do
|
70
|
+
expect(money.as_json).to eq("1.00")
|
71
|
+
end
|
72
|
+
|
73
|
+
it "is constructable with a BigDecimal" do
|
74
|
+
expect(Money.new(BigDecimal.new("1.23"))).to eq(Money.new(1.23))
|
75
|
+
end
|
76
|
+
|
77
|
+
it "is constructable with a Fixnum" do
|
78
|
+
expect(Money.new(3)).to eq(Money.new(3.00))
|
79
|
+
end
|
80
|
+
|
81
|
+
it "is construcatable with a Float" do
|
82
|
+
expect(Money.new(3.00)).to eq(Money.new(BigDecimal.new('3.00')))
|
83
|
+
end
|
84
|
+
|
85
|
+
it "is construcatable with a String" do
|
86
|
+
expect(Money.new('3.00')).to eq(Money.new(3.00))
|
87
|
+
end
|
88
|
+
|
89
|
+
it "is aware of the currency" do
|
90
|
+
expect(Money.new(1.00, 'CAD').currency.iso_code).to eq('CAD')
|
91
|
+
end
|
92
|
+
|
93
|
+
it "is addable" do
|
94
|
+
expect((Money.new(1.51) + Money.new(3.49))).to eq(Money.new(5.00))
|
95
|
+
end
|
96
|
+
|
97
|
+
it "keeps currency across calculations" do
|
98
|
+
expect(Money.new(1, 'USD') - Money.new(1, 'USD') + Money.new(1.23, Money::NULL_CURRENCY)).to eq(Money.new(1.23, 'USD'))
|
99
|
+
end
|
100
|
+
|
101
|
+
it "raises error if added other is not compatible" do
|
102
|
+
expect{ Money.new(5.00) + nil }.to raise_error(TypeError)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "is able to add $0 + $0" do
|
106
|
+
expect((Money.new + Money.new)).to eq(Money.new)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "adds inconsistent currencies" do
|
110
|
+
expect(Money).to receive(:deprecate).once
|
111
|
+
expect(Money.new(5, 'USD') + Money.new(1, 'CAD')).to eq(Money.new(6, 'USD'))
|
112
|
+
end
|
113
|
+
|
114
|
+
it "is subtractable" do
|
115
|
+
expect((Money.new(5.00) - Money.new(3.49))).to eq(Money.new(1.51))
|
116
|
+
end
|
117
|
+
|
118
|
+
it "raises error if subtracted other is not compatible" do
|
119
|
+
expect{ Money.new(5.00) - nil }.to raise_error(TypeError)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "is subtractable to $0" do
|
123
|
+
expect((Money.new(5.00) - Money.new(5.00))).to eq(Money.new)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "logs a deprecation warning when adding across currencies" do
|
127
|
+
expect(Money).to receive(:deprecate)
|
128
|
+
expect(Money.new(10, 'USD') - Money.new(1, 'JPY')).to eq(Money.new(9, 'USD'))
|
129
|
+
end
|
130
|
+
|
131
|
+
it "keeps currency when doing a computation with a null currency" do
|
132
|
+
currency = Money.new(10, 'JPY')
|
133
|
+
no_currency = Money.new(1, Money::NULL_CURRENCY)
|
134
|
+
expect((no_currency + currency).currency).to eq(Money::Currency.find!('JPY'))
|
135
|
+
expect((currency - no_currency).currency).to eq(Money::Currency.find!('JPY'))
|
136
|
+
end
|
137
|
+
|
138
|
+
it "does not log a deprecation warning when adding with a null currency value" do
|
139
|
+
currency = Money.new(10, 'USD')
|
140
|
+
no_currency = Money.new(1, Money::NULL_CURRENCY)
|
141
|
+
expect(Money).not_to receive(:deprecate)
|
142
|
+
expect(no_currency + currency).to eq(Money.new(11, 'USD'))
|
143
|
+
expect(currency - no_currency).to eq(Money.new(9, 'USD'))
|
144
|
+
end
|
145
|
+
|
146
|
+
it "is substractable to a negative amount" do
|
147
|
+
expect((Money.new(0.00) - Money.new(1.00))).to eq(Money.new("-1.00"))
|
148
|
+
end
|
149
|
+
|
150
|
+
it "is never negative zero" do
|
151
|
+
expect(Money.new(-0.00).to_s).to eq("0.00")
|
152
|
+
expect((Money.new(0) * -1).to_s).to eq("0.00")
|
153
|
+
end
|
154
|
+
|
155
|
+
it "#inspects to a presentable string" do
|
156
|
+
expect(money.inspect).to eq("#<Money value:1.00 currency:CAD>")
|
157
|
+
expect(Money.new(1, 'JPY').inspect).to eq("#<Money value:1 currency:JPY>")
|
158
|
+
expect(Money.new(1, 'JOD').inspect).to eq("#<Money value:1.000 currency:JOD>")
|
159
|
+
end
|
160
|
+
|
161
|
+
it "is inspectable within an array" do
|
162
|
+
expect([money].inspect).to eq("[#<Money value:1.00 currency:CAD>]")
|
163
|
+
end
|
164
|
+
|
165
|
+
it "correctly support eql? as a value object" do
|
166
|
+
expect(money).to eq(Money.new(1))
|
167
|
+
expect(money).to eq(Money.new(1, 'CAD'))
|
168
|
+
end
|
169
|
+
|
170
|
+
it "does not eql? with a non money object" do
|
171
|
+
expect(money).to_not eq(1)
|
172
|
+
expect(money).to_not eq(OpenStruct.new(value: 1))
|
173
|
+
end
|
174
|
+
|
175
|
+
it "does not eql? when currency missmatch" do
|
176
|
+
expect(money).to_not eq(Money.new(1, 'JPY'))
|
177
|
+
end
|
178
|
+
|
179
|
+
it "is addable with integer" do
|
180
|
+
expect((Money.new(1.33) + 1)).to eq(Money.new(2.33))
|
181
|
+
expect((1 + Money.new(1.33))).to eq(Money.new(2.33))
|
182
|
+
end
|
183
|
+
|
184
|
+
it "is addable with float" do
|
185
|
+
expect((Money.new(1.33) + 1.50)).to eq(Money.new(2.83))
|
186
|
+
expect((1.50 + Money.new(1.33))).to eq(Money.new(2.83))
|
187
|
+
end
|
188
|
+
|
189
|
+
it "is subtractable with integer" do
|
190
|
+
expect((Money.new(1.66) - 1)).to eq(Money.new(0.66))
|
191
|
+
expect((2 - Money.new(1.66))).to eq(Money.new(0.34))
|
192
|
+
end
|
193
|
+
|
194
|
+
it "is subtractable with float" do
|
195
|
+
expect((Money.new(1.66) - 1.50)).to eq(Money.new(0.16))
|
196
|
+
expect((1.50 - Money.new(1.33))).to eq(Money.new(0.17))
|
197
|
+
end
|
198
|
+
|
199
|
+
it "is multipliable with an integer" do
|
200
|
+
expect((Money.new(1.00) * 55)).to eq(Money.new(55.00))
|
201
|
+
expect((55 * Money.new(1.00))).to eq(Money.new(55.00))
|
202
|
+
end
|
203
|
+
|
204
|
+
it "is multiplable with a float" do
|
205
|
+
expect((Money.new(1.00) * 1.50)).to eq(Money.new(1.50))
|
206
|
+
expect((1.50 * Money.new(1.00))).to eq(Money.new(1.50))
|
207
|
+
end
|
208
|
+
|
209
|
+
it "is multipliable by a cents amount" do
|
210
|
+
expect((Money.new(1.00) * 0.50)).to eq(Money.new(0.50))
|
211
|
+
expect((0.50 * Money.new(1.00))).to eq(Money.new(0.50))
|
212
|
+
end
|
213
|
+
|
214
|
+
it "is multipliable by a rational" do
|
215
|
+
expect((Money.new(3.3) * Rational(1, 12))).to eq(Money.new(0.28))
|
216
|
+
expect((Rational(1, 12) * Money.new(3.3))).to eq(Money.new(0.28))
|
217
|
+
end
|
218
|
+
|
219
|
+
it "is multipliable by a repeatable floating point number" do
|
220
|
+
expect((Money.new(24.00) * (1 / 30.0))).to eq(Money.new(0.80))
|
221
|
+
expect(((1 / 30.0) * Money.new(24.00))).to eq(Money.new(0.80))
|
222
|
+
end
|
223
|
+
|
224
|
+
it "is multipliable by a repeatable floating point number where the floating point error rounds down" do
|
225
|
+
expect((Money.new(3.3) * (1.0 / 12))).to eq(Money.new(0.28))
|
226
|
+
expect(((1.0 / 12) * Money.new(3.3))).to eq(Money.new(0.28))
|
227
|
+
end
|
228
|
+
|
229
|
+
it "is multipliable by a money object" do
|
230
|
+
expect(Money).to receive(:deprecate).once
|
231
|
+
expect((Money.new(3.3) * Money.new(1))).to eq(Money.new(3.3))
|
232
|
+
end
|
233
|
+
|
234
|
+
it "rounds multiplication result with fractional penny of 5 or higher up" do
|
235
|
+
expect((Money.new(0.03) * 0.5)).to eq(Money.new(0.02))
|
236
|
+
expect((0.5 * Money.new(0.03))).to eq(Money.new(0.02))
|
237
|
+
end
|
238
|
+
|
239
|
+
it "rounds multiplication result with fractional penny of 4 or lower down" do
|
240
|
+
expect((Money.new(0.10) * 0.33)).to eq(Money.new(0.03))
|
241
|
+
expect((0.33 * Money.new(0.10))).to eq(Money.new(0.03))
|
242
|
+
end
|
243
|
+
|
244
|
+
it "is less than a bigger integer" do
|
245
|
+
expect(Money.new(1)).to be < 2
|
246
|
+
expect(2).to be > Money.new(1)
|
247
|
+
end
|
248
|
+
|
249
|
+
it "is less than or equal to a bigger integer" do
|
250
|
+
expect(Money.new(1)).to be <= 2
|
251
|
+
expect(2).to be >= Money.new(1)
|
252
|
+
end
|
253
|
+
|
254
|
+
it "is greater than a lesser integer" do
|
255
|
+
expect(Money.new(2)).to be > 1
|
256
|
+
expect(1).to be < Money.new(2)
|
257
|
+
end
|
258
|
+
|
259
|
+
it "is greater than or equal to a lesser integer" do
|
260
|
+
expect(Money.new(2)).to be >= 1
|
261
|
+
expect(1).to be <= Money.new(2)
|
262
|
+
end
|
263
|
+
|
264
|
+
it "raises if divided" do
|
265
|
+
expect { Money.new(55.00) / 55 }.to raise_error(RuntimeError)
|
266
|
+
end
|
267
|
+
|
268
|
+
it "returns cents in to_liquid" do
|
269
|
+
expect(Money.new(1.00).to_liquid).to eq(100)
|
270
|
+
end
|
271
|
+
|
272
|
+
it "returns cents in to_json" do
|
273
|
+
expect(Money.new(1.00).to_json).to eq("1.00")
|
274
|
+
end
|
275
|
+
|
276
|
+
it "supports absolute value" do
|
277
|
+
expect(Money.new(-1.00).abs).to eq(Money.new(1.00))
|
278
|
+
end
|
279
|
+
|
280
|
+
it "supports to_i" do
|
281
|
+
expect(Money.new(1.50).to_i).to eq(1)
|
282
|
+
end
|
283
|
+
|
284
|
+
it "supports to_d" do
|
285
|
+
expect(Money.new(1.29).to_d).to eq(BigDecimal.new('1.29'))
|
286
|
+
end
|
287
|
+
|
288
|
+
it "supports to_f" do
|
289
|
+
expect(Money.new(1.50).to_f.to_s).to eq("1.5")
|
290
|
+
end
|
291
|
+
|
292
|
+
it "is creatable from an integer value in cents" do
|
293
|
+
expect(Money.from_cents(1950)).to eq(Money.new(19.50))
|
294
|
+
end
|
295
|
+
|
296
|
+
it "is creatable from an integer value of 0 in cents" do
|
297
|
+
expect(Money.from_cents(0)).to eq(Money.new)
|
298
|
+
end
|
299
|
+
|
300
|
+
it "is creatable from a float cents amount" do
|
301
|
+
expect(Money.from_cents(1950.5)).to eq(Money.new(19.51))
|
302
|
+
end
|
303
|
+
|
304
|
+
it "is creatable from an integer value in cents and currency" do
|
305
|
+
expect(Money.from_subunits(1950, 'CAD')).to eq(Money.new(19.50))
|
306
|
+
end
|
307
|
+
|
308
|
+
it "is creatable from an integer value in dollars and currency with no cents" do
|
309
|
+
expect(Money.from_subunits(1950, 'JPY')).to eq(Money.new(1950, 'JPY'))
|
310
|
+
end
|
311
|
+
|
312
|
+
it "raises when constructed with a NaN value" do
|
313
|
+
expect { Money.new( 0.0 / 0) }.to raise_error(ArgumentError)
|
314
|
+
end
|
315
|
+
|
316
|
+
it "is comparable with non-money objects" do
|
317
|
+
expect(money).not_to eq(nil)
|
318
|
+
end
|
319
|
+
|
320
|
+
it "supports floor" do
|
321
|
+
expect(Money.new(15.52).floor).to eq(Money.new(15.00))
|
322
|
+
expect(Money.new(18.99).floor).to eq(Money.new(18.00))
|
323
|
+
expect(Money.new(21).floor).to eq(Money.new(21))
|
324
|
+
end
|
325
|
+
|
326
|
+
it "generates a true rational" do
|
327
|
+
expect(Money.rational(Money.new(10.0), Money.new(15.0))).to eq(Rational(2,3))
|
328
|
+
expect(Money).to receive(:deprecate).once
|
329
|
+
expect(Money.rational(Money.new(10.0, 'USD'), Money.new(15.0, 'JPY'))).to eq(Rational(2,3))
|
330
|
+
end
|
331
|
+
|
332
|
+
describe "frozen with amount of $1" do
|
333
|
+
let (:money) { Money.new(1.00) }
|
334
|
+
|
335
|
+
it "is equals to $1" do
|
336
|
+
expect(money).to eq(Money.new(1.00))
|
337
|
+
end
|
338
|
+
|
339
|
+
it "is not equals to $2" do
|
340
|
+
expect(money).not_to eq(Money.new(2.00))
|
341
|
+
end
|
342
|
+
|
343
|
+
it "<=> $1 is 0" do
|
344
|
+
expect((money <=> Money.new(1.00))).to eq(0)
|
345
|
+
end
|
346
|
+
|
347
|
+
it "<=> $2 is -1" do
|
348
|
+
expect((money <=> Money.new(2.00))).to eq(-1)
|
349
|
+
end
|
350
|
+
|
351
|
+
it "<=> $0.50 equals 1" do
|
352
|
+
expect((money <=> Money.new(0.50))).to eq(1)
|
353
|
+
end
|
354
|
+
|
355
|
+
it "<=> works with non-money objects" do
|
356
|
+
expect((money <=> 1)).to eq(0)
|
357
|
+
expect((money <=> 2)).to eq(-1)
|
358
|
+
expect((money <=> 0.5)).to eq(1)
|
359
|
+
expect((1 <=> money)).to eq(0)
|
360
|
+
expect((2 <=> money)).to eq(1)
|
361
|
+
expect((0.5 <=> money)).to eq(-1)
|
362
|
+
end
|
363
|
+
|
364
|
+
it "raises error if compared other is not compatible" do
|
365
|
+
expect{ Money.new(5.00) <=> nil }.to raise_error(TypeError)
|
366
|
+
end
|
367
|
+
|
368
|
+
it "have the same hash value as $1" do
|
369
|
+
expect(money.hash).to eq(Money.new(1.00).hash)
|
370
|
+
end
|
371
|
+
|
372
|
+
it "does not have the same hash value as $2" do
|
373
|
+
expect(money.hash).to eq(Money.new(1.00).hash)
|
374
|
+
end
|
375
|
+
|
376
|
+
it "<=> can compare with and without currency" do
|
377
|
+
expect(Money.new(1000, Money::NULL_CURRENCY) <=> Money.new(2000, 'JPY')).to eq(-1)
|
378
|
+
expect(Money.new(2000, 'JPY') <=> Money.new(1000, Money::NULL_CURRENCY)).to eq(1)
|
379
|
+
end
|
380
|
+
|
381
|
+
it "<=> issues deprecation warning when comparing incompatible currency" do
|
382
|
+
expect(Money).to receive(:deprecate).twice
|
383
|
+
expect(Money.new(1000, 'USD') <=> Money.new(2000, 'JPY')).to eq(-1)
|
384
|
+
expect(Money.new(2000, 'JPY') <=> Money.new(1000, 'USD')).to eq(1)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
describe "#subunits" do
|
389
|
+
it 'multiplies by the number of decimal places for the currency' do
|
390
|
+
expect(Money.new(1, 'USD').subunits).to eq(100)
|
391
|
+
expect(Money.new(1, 'JPY').subunits).to eq(1)
|
392
|
+
expect(Money.new(1, 'IQD').subunits).to eq(1000)
|
393
|
+
expect(Money.new(1).subunits).to eq(100)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
describe "value" do
|
398
|
+
it "rounds to the number of minor units provided by the currency" do
|
399
|
+
expect(Money.new(1.1111, 'USD').value).to eq(1.11)
|
400
|
+
expect(Money.new(1.1111, 'JPY').value).to eq(1)
|
401
|
+
expect(Money.new(1.1111, 'IQD').value).to eq(1.111)
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
describe "with amount of $0" do
|
406
|
+
let (:money) { Money.new(0) }
|
407
|
+
|
408
|
+
it "is zero" do
|
409
|
+
expect(money).to be_zero
|
410
|
+
end
|
411
|
+
|
412
|
+
it "is greater than -$1" do
|
413
|
+
expect(money).to be > Money.new("-1.00")
|
414
|
+
end
|
415
|
+
|
416
|
+
it "is greater than or equal to $0" do
|
417
|
+
expect(money).to be >= Money.new
|
418
|
+
end
|
419
|
+
|
420
|
+
it "is less than or equal to $0" do
|
421
|
+
expect(money).to be <= Money.new
|
422
|
+
end
|
423
|
+
|
424
|
+
it "is less than $1" do
|
425
|
+
expect(money).to be < Money.new(1.00)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
describe "with amount of $1" do
|
430
|
+
let (:money) { Money.new(1.00) }
|
431
|
+
|
432
|
+
it "is not zero" do
|
433
|
+
expect(money).not_to be_zero
|
434
|
+
end
|
435
|
+
|
436
|
+
it "returns cents as a decimal value = 1.00" do
|
437
|
+
expect(money.value).to eq(BigDecimal.new("1.00"))
|
438
|
+
end
|
439
|
+
|
440
|
+
it "returns cents as 100 cents" do
|
441
|
+
expect(money.cents).to eq(100)
|
442
|
+
end
|
443
|
+
|
444
|
+
it "returns cents as 100 cents" do
|
445
|
+
expect(money.subunits).to eq(100)
|
446
|
+
end
|
447
|
+
|
448
|
+
it "returns cents as a Fixnum" do
|
449
|
+
expect(money.subunits).to be_an_instance_of(Fixnum)
|
450
|
+
end
|
451
|
+
|
452
|
+
it "is greater than $0" do
|
453
|
+
expect(money).to be > Money.new(0.00)
|
454
|
+
end
|
455
|
+
|
456
|
+
it "is less than $2" do
|
457
|
+
expect(money).to be < Money.new(2.00)
|
458
|
+
end
|
459
|
+
|
460
|
+
it "is equal to $1" do
|
461
|
+
expect(money).to eq(Money.new(1.00))
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
describe "allocation"do
|
466
|
+
specify "#allocate takes no action when one gets all" do
|
467
|
+
expect(Money.new(5).allocate([1])).to eq([Money.new(5)])
|
468
|
+
end
|
469
|
+
|
470
|
+
specify "#allocate does not lose pennies" do
|
471
|
+
moneys = Money.new(0.05).allocate([0.3,0.7])
|
472
|
+
expect(moneys[0]).to eq(Money.new(0.02))
|
473
|
+
expect(moneys[1]).to eq(Money.new(0.03))
|
474
|
+
end
|
475
|
+
|
476
|
+
specify "#allocate does not lose dollars with non-decimal currency" do
|
477
|
+
moneys = Money.new(5, 'JPY').allocate([0.3,0.7])
|
478
|
+
expect(moneys[0]).to eq(Money.new(2, 'JPY'))
|
479
|
+
expect(moneys[1]).to eq(Money.new(3, 'JPY'))
|
480
|
+
end
|
481
|
+
|
482
|
+
specify "#allocate does not lose dollars with three decimal currency" do
|
483
|
+
moneys = Money.new(0.005, 'JOD').allocate([0.3,0.7])
|
484
|
+
expect(moneys[0]).to eq(Money.new(0.002, 'JOD'))
|
485
|
+
expect(moneys[1]).to eq(Money.new(0.003, 'JOD'))
|
486
|
+
end
|
487
|
+
|
488
|
+
specify "#allocate does not lose pennies even when given a lossy split" do
|
489
|
+
moneys = Money.new(1).allocate([0.333,0.333, 0.333])
|
490
|
+
expect(moneys[0].subunits).to eq(34)
|
491
|
+
expect(moneys[1].subunits).to eq(33)
|
492
|
+
expect(moneys[2].subunits).to eq(33)
|
493
|
+
end
|
494
|
+
|
495
|
+
specify "#allocate requires total to be less than 1" do
|
496
|
+
expect { Money.new(0.05).allocate([0.5,0.6]) }.to raise_error(ArgumentError)
|
497
|
+
end
|
498
|
+
|
499
|
+
specify "#allocate will use rationals if provided" do
|
500
|
+
splits = [128400,20439,14589,14589,25936].map{ |num| Rational(num, 203953) } # sums to > 1 if converted to float
|
501
|
+
expect(Money.new(2.25).allocate(splits)).to eq([Money.new(1.42), Money.new(0.23), Money.new(0.16), Money.new(0.16), Money.new(0.28)])
|
502
|
+
end
|
503
|
+
|
504
|
+
specify "#allocate will convert rationals with high precision" do
|
505
|
+
ratios = [Rational(1, 1), Rational(0)]
|
506
|
+
expect(Money.new("858993456.12").allocate(ratios)).to eq([Money.new("858993456.12"), Money.empty])
|
507
|
+
ratios = [Rational(1, 6), Rational(5, 6)]
|
508
|
+
expect(Money.new("3.00").allocate(ratios)).to eq([Money.new("0.50"), Money.new("2.50")])
|
509
|
+
end
|
510
|
+
|
511
|
+
specify "#allocate doesn't raise with weird negative rational ratios" do
|
512
|
+
rate = Rational(-5, 1201)
|
513
|
+
expect { Money.new(1).allocate([rate, 1 - rate]) }.not_to raise_error
|
514
|
+
end
|
515
|
+
|
516
|
+
specify "#allocate_max_amounts returns the weighted allocation without exceeding the maxima when there is room for the remainder" do
|
517
|
+
expect(
|
518
|
+
Money.new(30.75).allocate_max_amounts([Money.new(26), Money.new(4.75)]),
|
519
|
+
).to eq([Money.new(26), Money.new(4.75)])
|
520
|
+
end
|
521
|
+
|
522
|
+
specify "#allocate_max_amounts returns the weighted allocation without exceeding the maxima when there is room for the remainder with currency" do
|
523
|
+
expect(
|
524
|
+
Money.new(3075, 'JPY').allocate_max_amounts([Money.new(2600, 'JPY'), Money.new(475, 'JPY')]),
|
525
|
+
).to eq([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
526
|
+
end
|
527
|
+
|
528
|
+
specify "#allocate_max_amounts legal computation with no currency objects" do
|
529
|
+
expect(
|
530
|
+
Money.new(3075, 'JPY').allocate_max_amounts([2600, 475]),
|
531
|
+
).to eq([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
532
|
+
|
533
|
+
expect(
|
534
|
+
Money.new(3075, Money::NULL_CURRENCY).allocate_max_amounts([Money.new(2600, 'JPY'), Money.new(475, 'JPY')]),
|
535
|
+
).to eq([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
536
|
+
end
|
537
|
+
|
538
|
+
specify "#allocate_max_amounts illegal computation across currencies" do
|
539
|
+
expect {
|
540
|
+
Money.new(3075, 'USD').allocate_max_amounts([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
541
|
+
}.to raise_error(ArgumentError)
|
542
|
+
end
|
543
|
+
|
544
|
+
specify "#allocate_max_amounts drops the remainder when returning the weighted allocation without exceeding the maxima when there is no room for the remainder" do
|
545
|
+
expect(
|
546
|
+
Money.new(30.75).allocate_max_amounts([Money.new(26), Money.new(4.74)]),
|
547
|
+
).to eq([Money.new(26), Money.new(4.74)])
|
548
|
+
end
|
549
|
+
|
550
|
+
specify "#allocate_max_amounts returns the weighted allocation when there is no remainder" do
|
551
|
+
expect(
|
552
|
+
Money.new(30).allocate_max_amounts([Money.new(15), Money.new(15)]),
|
553
|
+
).to eq([Money.new(15), Money.new(15)])
|
554
|
+
end
|
555
|
+
|
556
|
+
specify "#allocate_max_amounts allocates the remainder round-robin when the maxima are not reached" do
|
557
|
+
expect(
|
558
|
+
Money.new(1).allocate_max_amounts([Money.new(33), Money.new(33), Money.new(33)]),
|
559
|
+
).to eq([Money.new(0.34), Money.new(0.33), Money.new(0.33)])
|
560
|
+
end
|
561
|
+
|
562
|
+
specify "#allocate_max_amounts allocates up to the maxima specified" do
|
563
|
+
expect(
|
564
|
+
Money.new(100).allocate_max_amounts([Money.new(5), Money.new(2)]),
|
565
|
+
).to eq([Money.new(5), Money.new(2)])
|
566
|
+
end
|
567
|
+
|
568
|
+
specify "#allocate_max_amounts supports all-zero maxima" do
|
569
|
+
expect(
|
570
|
+
Money.new(3).allocate_max_amounts([Money.empty, Money.empty, Money.empty]),
|
571
|
+
).to eq([Money.empty, Money.empty, Money.empty])
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
describe "split" do
|
576
|
+
specify "#split needs at least one party" do
|
577
|
+
expect {Money.new(1).split(0)}.to raise_error(ArgumentError)
|
578
|
+
expect {Money.new(1).split(-1)}.to raise_error(ArgumentError)
|
579
|
+
end
|
580
|
+
|
581
|
+
specify "#gives 1 cent to both people if we start with 2" do
|
582
|
+
expect(Money.new(0.02).split(2)).to eq([Money.new(0.01), Money.new(0.01)])
|
583
|
+
end
|
584
|
+
|
585
|
+
specify "#split may distribute no money to some parties if there isnt enough to go around" do
|
586
|
+
expect(Money.new(0.02).split(3)).to eq([Money.new(0.01), Money.new(0.01), Money.new(0)])
|
587
|
+
end
|
588
|
+
|
589
|
+
specify "#split does not lose pennies" do
|
590
|
+
expect(Money.new(0.05).split(2)).to eq([Money.new(0.03), Money.new(0.02)])
|
591
|
+
end
|
592
|
+
|
593
|
+
specify "#split does not lose dollars with non-decimal currencies" do
|
594
|
+
expect(Money.new(5, 'JPY').split(2)).to eq([Money.new(3, 'JPY'), Money.new(2, 'JPY')])
|
595
|
+
end
|
596
|
+
|
597
|
+
specify "#split a dollar" do
|
598
|
+
moneys = Money.new(1).split(3)
|
599
|
+
expect(moneys[0].subunits).to eq(34)
|
600
|
+
expect(moneys[1].subunits).to eq(33)
|
601
|
+
expect(moneys[2].subunits).to eq(33)
|
602
|
+
end
|
603
|
+
|
604
|
+
specify "#split a 100 yen" do
|
605
|
+
moneys = Money.new(100, 'JPY').split(3)
|
606
|
+
expect(moneys[0].value).to eq(34)
|
607
|
+
expect(moneys[1].value).to eq(33)
|
608
|
+
expect(moneys[2].value).to eq(33)
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
612
|
+
describe "fraction" do
|
613
|
+
specify "#fraction needs a positive rate" do
|
614
|
+
expect {Money.new(1).fraction(-0.5)}.to raise_error(ArgumentError)
|
615
|
+
end
|
616
|
+
|
617
|
+
specify "#fraction returns the amount minus a fraction" do
|
618
|
+
expect(Money.new(1.15).fraction(0.15)).to eq(Money.new(1.00))
|
619
|
+
expect(Money.new(2.50).fraction(0.15)).to eq(Money.new(2.17))
|
620
|
+
expect(Money.new(35.50).fraction(0)).to eq(Money.new(35.50))
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
describe "with amount of $1 with created with 3 decimal places" do
|
625
|
+
let (:money) { Money.new(1.125) }
|
626
|
+
|
627
|
+
it "rounds 3rd decimal place" do
|
628
|
+
expect(money.value).to eq(BigDecimal.new("1.13"))
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
describe "parser dependency injection" do
|
633
|
+
before(:each) { Money.parser = AccountingMoneyParser }
|
634
|
+
after(:each) { Money.parser = MoneyParser }
|
635
|
+
|
636
|
+
it "keeps AccountingMoneyParser class on new money objects" do
|
637
|
+
expect(Money.new.class.parser).to eq(AccountingMoneyParser)
|
638
|
+
end
|
639
|
+
|
640
|
+
it "supports parenthesis from AccountingMoneyParser" do
|
641
|
+
expect(Money.parse("($5.00)")).to eq(Money.new(-5))
|
642
|
+
end
|
643
|
+
|
644
|
+
it "supports parenthesis from AccountingMoneyParser for .to_money" do
|
645
|
+
expect("($5.00)".to_money).to eq(Money.new(-5))
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
describe "round" do
|
650
|
+
|
651
|
+
it "rounds to 0 decimal places by default" do
|
652
|
+
expect(Money.new(54.1).round).to eq(Money.new(54))
|
653
|
+
expect(Money.new(54.5).round).to eq(Money.new(55))
|
654
|
+
end
|
655
|
+
|
656
|
+
# Overview of standard vs. banker's rounding for next 4 specs:
|
657
|
+
# http://www.xbeat.net/vbspeed/i_BankersRounding.htm
|
658
|
+
it "implements standard rounding for 2 digits" do
|
659
|
+
expect(Money.new(54.1754).round(2)).to eq(Money.new(54.18))
|
660
|
+
expect(Money.new(343.2050).round(2)).to eq(Money.new(343.21))
|
661
|
+
expect(Money.new(106.2038).round(2)).to eq(Money.new(106.20))
|
662
|
+
end
|
663
|
+
|
664
|
+
it "implements standard rounding for 1 digit" do
|
665
|
+
expect(Money.new(27.25).round(1)).to eq(Money.new(27.3))
|
666
|
+
expect(Money.new(27.45).round(1)).to eq(Money.new(27.5))
|
667
|
+
expect(Money.new(27.55).round(1)).to eq(Money.new(27.6))
|
668
|
+
end
|
669
|
+
|
670
|
+
end
|
671
|
+
|
672
|
+
describe "from_amount quacks like RubyMoney" do
|
673
|
+
it "accepts numeric values" do
|
674
|
+
expect(Money.from_amount(1)).to eq Money.from_cents(1_00)
|
675
|
+
expect(Money.from_amount(1.0)).to eq Money.from_cents(1_00)
|
676
|
+
expect(Money.from_amount(BigDecimal.new("1"))).to eq Money.from_cents(1_00)
|
677
|
+
end
|
678
|
+
|
679
|
+
it "accepts string values" do
|
680
|
+
expect(Money.from_amount("1")).to eq Money.from_cents(1_00)
|
681
|
+
end
|
682
|
+
|
683
|
+
it "accepts nil values" do
|
684
|
+
expect(Money.from_amount(nil)).to eq Money.from_cents(0)
|
685
|
+
end
|
686
|
+
|
687
|
+
it "accepts an optional currency parameter" do
|
688
|
+
expect { Money.from_amount(1, "CAD") }.to_not raise_error
|
689
|
+
end
|
690
|
+
|
691
|
+
it "accepts Rationla number" do
|
692
|
+
expect(Money.from_amount(Rational("999999999999999999.999")).value).to eql(BigDecimal.new("1000000000000000000", Money::Helpers::MAX_DECIMAL))
|
693
|
+
expect(Money.from_amount(Rational("999999999999999999.99")).value).to eql(BigDecimal.new("999999999999999999.99", Money::Helpers::MAX_DECIMAL))
|
694
|
+
end
|
695
|
+
|
696
|
+
it "raises ArgumentError with unsupported argument" do
|
697
|
+
expect { Money.from_amount(Object.new) }.to raise_error(ArgumentError)
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
describe "YAML serialization" do
|
702
|
+
it "accepts values with currencies" do
|
703
|
+
money = YAML.dump(Money.new(750, 'usd'))
|
704
|
+
expect(money).to eq("--- !ruby/object:Money\nvalue: '750.0'\ncurrency: USD\n")
|
705
|
+
end
|
706
|
+
end
|
707
|
+
|
708
|
+
describe "YAML deserialization" do
|
709
|
+
|
710
|
+
it "accepts values with currencies" do
|
711
|
+
money = YAML.load("--- !ruby/object:Money\nvalue: '750.0'\ncurrency: USD\n")
|
712
|
+
expect(money).to eq(Money.new(750, 'usd'))
|
713
|
+
end
|
714
|
+
|
715
|
+
it "accepts values with null currencies" do
|
716
|
+
money = YAML.load("--- !ruby/object:Money\nvalue: '750.0'\ncurrency: XXX\n")
|
717
|
+
expect(money).to eq(Money.new(750))
|
718
|
+
end
|
719
|
+
|
720
|
+
it "accepts serialized NullCurrency objects" do
|
721
|
+
money = YAML.load(<<~EOS)
|
722
|
+
---
|
723
|
+
!ruby/object:Money
|
724
|
+
currency: !ruby/object:Money::NullCurrency
|
725
|
+
symbol: >-
|
726
|
+
$
|
727
|
+
disambiguate_symbol:
|
728
|
+
iso_code: >-
|
729
|
+
XXX
|
730
|
+
iso_numeric: >-
|
731
|
+
999
|
732
|
+
name: >-
|
733
|
+
No Currency
|
734
|
+
smallest_denomination: 1
|
735
|
+
subunit_to_unit: 100
|
736
|
+
minor_units: 2
|
737
|
+
value: !ruby/object:BigDecimal 27:0.6935E2
|
738
|
+
cents: 6935
|
739
|
+
EOS
|
740
|
+
expect(money).to eq(Money.new(69.35, Money::NULL_CURRENCY))
|
741
|
+
end
|
742
|
+
|
743
|
+
it "accepts BigDecimal values" do
|
744
|
+
money = YAML.load(<<~EOS)
|
745
|
+
---
|
746
|
+
!ruby/object:Money
|
747
|
+
value: !ruby/object:BigDecimal 18:0.75E3
|
748
|
+
cents: 75000
|
749
|
+
EOS
|
750
|
+
expect(money).to be == Money.new(750)
|
751
|
+
expect(money.value).to be_a BigDecimal
|
752
|
+
end
|
753
|
+
|
754
|
+
it "accepts old float values..." do
|
755
|
+
money = YAML.load(<<~EOS)
|
756
|
+
---
|
757
|
+
!ruby/object:Money
|
758
|
+
value: 750.00
|
759
|
+
cents: 75000
|
760
|
+
EOS
|
761
|
+
expect(money).to be == Money.new(750)
|
762
|
+
expect(money.value).to be_a BigDecimal
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
766
|
+
describe('.deprecate') do
|
767
|
+
it "uses ruby warn if active support is not defined" do
|
768
|
+
stub_const("ACTIVE_SUPPORT_DEFINED", false)
|
769
|
+
expect(Kernel).to receive(:warn).once
|
770
|
+
Money.deprecate('ok')
|
771
|
+
end
|
772
|
+
|
773
|
+
it "uses active support warn if active support is defined" do
|
774
|
+
expect(Kernel).to receive(:warn).never
|
775
|
+
expect_any_instance_of(ActiveSupport::Deprecation).to receive(:warn).once
|
776
|
+
Money.deprecate('ok')
|
777
|
+
end
|
778
|
+
|
779
|
+
it "only sends a callstack of events outside of the money gem" do
|
780
|
+
expect_any_instance_of(ActiveSupport::Deprecation).to receive(:warn).with(
|
781
|
+
-> (message) { message == "[Shopify/Money] message\n" },
|
782
|
+
-> (callstack) { !callstack.first.to_s.include?('gems/money') && callstack.size > 0 }
|
783
|
+
)
|
784
|
+
Money.deprecate('message')
|
785
|
+
end
|
786
|
+
end
|
787
|
+
|
788
|
+
describe '#use_currency' do
|
789
|
+
it "allows setting the implicit default currency for a block scope" do
|
790
|
+
money = nil
|
791
|
+
Money.with_currency('CAD') do
|
792
|
+
money = Money.new(1.00)
|
793
|
+
end
|
794
|
+
|
795
|
+
expect(money.currency.iso_code).to eq('CAD')
|
796
|
+
end
|
797
|
+
|
798
|
+
it "does not use the currency for a block scope when explicitly set" do
|
799
|
+
money = nil
|
800
|
+
Money.with_currency('CAD') do
|
801
|
+
money = Money.new(1.00, 'USD')
|
802
|
+
end
|
803
|
+
|
804
|
+
expect(money.currency.iso_code).to eq('USD')
|
805
|
+
end
|
806
|
+
|
807
|
+
context "with .default_currency set" do
|
808
|
+
before(:each) { Money.default_currency = Money::Currency.new('EUR') }
|
809
|
+
after(:each) { Money.default_currency = Money::NULL_CURRENCY }
|
810
|
+
|
811
|
+
it "can be nested and falls back to default_currency outside of the blocks" do
|
812
|
+
money2, money3 = nil
|
813
|
+
|
814
|
+
money1 = Money.new(1.00)
|
815
|
+
Money.with_currency('CAD') do
|
816
|
+
Money.with_currency('USD') do
|
817
|
+
money2 = Money.new(1.00)
|
818
|
+
end
|
819
|
+
money3 = Money.new(1.00)
|
820
|
+
end
|
821
|
+
money4 = Money.new(1.00)
|
822
|
+
|
823
|
+
expect(money1.currency.iso_code).to eq('EUR')
|
824
|
+
expect(money2.currency.iso_code).to eq('USD')
|
825
|
+
expect(money3.currency.iso_code).to eq('CAD')
|
826
|
+
expect(money4.currency.iso_code).to eq('EUR')
|
827
|
+
end
|
828
|
+
end
|
829
|
+
end
|
830
|
+
|
831
|
+
describe '.clamp' do
|
832
|
+
let(:max) { 9000 }
|
833
|
+
let(:min) { -max }
|
834
|
+
|
835
|
+
it 'returns the same value if the value is within the min..max range' do
|
836
|
+
money = Money.new(5000, 'EUR').clamp(min, max)
|
837
|
+
expect(money.value).to eq(5000)
|
838
|
+
expect(money.currency.iso_code).to eq('EUR')
|
839
|
+
end
|
840
|
+
|
841
|
+
it 'returns the max value if the original value is larger' do
|
842
|
+
money = Money.new(9001, 'EUR').clamp(min, max)
|
843
|
+
expect(money.clamp(min, max).value).to eq(9000)
|
844
|
+
expect(money.clamp(min, max).currency.iso_code).to eq('EUR')
|
845
|
+
end
|
846
|
+
|
847
|
+
it 'returns the min value if the original value is smaller' do
|
848
|
+
money = Money.new(-9001, 'EUR').clamp(min, max)
|
849
|
+
expect(money.value).to eq(-9000)
|
850
|
+
expect(money.currency.iso_code).to eq('EUR')
|
851
|
+
end
|
852
|
+
end
|
853
|
+
end
|