amounts 0.0.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +37 -0
- data/CHANGELOG.md +113 -0
- data/Gemfile +4 -0
- data/README.md +215 -1
- data/Rakefile +47 -10
- data/lib/amount/active_record/amount_validator.rb +9 -18
- data/lib/amount/active_record/attribute_definition.rb +3 -1
- data/lib/amount/active_record/locale/en.yml +7 -0
- data/lib/amount/active_record/model.rb +9 -0
- data/lib/amount/active_record.rb +12 -0
- data/lib/amount/compact.rb +137 -0
- data/lib/amount/{allocation.rb → concerns/allocatable.rb} +1 -1
- data/lib/amount/{arithmetic.rb → concerns/calculable.rb} +1 -1
- data/lib/amount/concerns/coercible.rb +65 -0
- data/lib/amount/concerns/constructible.rb +92 -0
- data/lib/amount/{conversion.rb → concerns/convertible.rb} +1 -1
- data/lib/amount/concerns/formattable.rb +112 -0
- data/lib/amount/concerns/localizable.rb +39 -0
- data/lib/amount/{comparison.rb → concerns/orderable.rb} +2 -2
- data/lib/amount/concerns/parsable.rb +58 -0
- data/lib/amount/concerns/registrable.rb +80 -0
- data/lib/amount/{serialization.rb → concerns/serializable.rb} +3 -3
- data/lib/amount/display.rb +81 -46
- data/lib/amount/locale.rb +102 -0
- data/lib/amount/registry.rb +69 -7
- data/lib/amount/ui_parser.rb +109 -0
- data/lib/amount/version.rb +1 -1
- data/lib/amount.rb +55 -199
- data/test/amount/active_record/amount_validator_test.rb +201 -0
- data/test/amount/active_record/attribute_definition_test.rb +234 -0
- data/test/amount/active_record/migration_methods_test.rb +60 -0
- data/test/amount/active_record/model_test.rb +40 -0
- data/test/amount/active_record/type_test.rb +80 -0
- data/test/amount/active_record_test.rb +12 -0
- data/test/amount/compact_test.rb +371 -0
- data/test/amount/concerns/allocatable_test.rb +166 -0
- data/test/amount/concerns/calculable_test.rb +152 -0
- data/test/amount/concerns/coercible_test.rb +49 -0
- data/test/amount/concerns/constructible_test.rb +113 -0
- data/test/amount/concerns/convertible_test.rb +54 -0
- data/test/amount/concerns/formattable_test.rb +336 -0
- data/test/amount/concerns/localizable_test.rb +59 -0
- data/test/amount/concerns/orderable_test.rb +83 -0
- data/test/amount/concerns/parsable_test.rb +98 -0
- data/test/amount/concerns/registrable_test.rb +67 -0
- data/test/amount/concerns/serializable_test.rb +98 -0
- data/test/amount/display_test.rb +181 -0
- data/test/amount/locale_test.rb +590 -0
- data/test/amount/parser_test.rb +29 -0
- data/test/amount/registry/generated_constructors_test.rb +94 -0
- data/test/amount/registry_test.rb +149 -0
- data/test/amount/ui_parser_test.rb +284 -0
- data/test/amount_test.rb +55 -0
- data/test/dummy/config/application.rb +3 -0
- data/test/generators/amount/active_record/registry_generator_test.rb +116 -0
- data/test/integration/postgresql_test.rb +94 -0
- data/test/support/active_record_test_support.rb +65 -0
- data/test/support/error_helpers.rb +22 -0
- data/test/support/locale_test_setup.rb +36 -0
- data/test/test_helper.rb +1 -0
- metadata +47 -11
- data/test/postgresql_integration_test.rb +0 -71
- data/test/test_active_record.rb +0 -375
- data/test/test_amount.rb +0 -675
- data/test/test_registry_generator.rb +0 -104
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "i18n"
|
|
4
|
+
require_relative "../../test_helper"
|
|
5
|
+
|
|
6
|
+
class AmountParsableTest < Minitest::Test
|
|
7
|
+
class GoldAmount < Amount
|
|
8
|
+
def purity = "24k"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def setup
|
|
12
|
+
AmountTestSupport.register_default_types!
|
|
13
|
+
@previous_locale = I18n.locale
|
|
14
|
+
@previous_backend = I18n.backend
|
|
15
|
+
I18n.backend = I18n::Backend::Simple.new
|
|
16
|
+
I18n.available_locales = %i[en de fr]
|
|
17
|
+
I18n.backend.store_translations(:en, number: { format: { delimiter: ",", separator: "." } })
|
|
18
|
+
I18n.backend.store_translations(:de, number: { format: { delimiter: ".", separator: "," } })
|
|
19
|
+
I18n.backend.store_translations(:fr, number: { format: { delimiter: " ", separator: "," } })
|
|
20
|
+
Amount.localize = false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def teardown
|
|
24
|
+
Amount.localize = false
|
|
25
|
+
I18n.backend = @previous_backend
|
|
26
|
+
I18n.locale = @previous_locale
|
|
27
|
+
Amount.registry.clear!
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_parse_reads_atomic_value
|
|
31
|
+
assert_equal 1_500_000, Amount.parse("USDC|1.5").atomic
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_parse_roundtrips_to_s
|
|
35
|
+
assert_equal "USDC|1.50", Amount.parse("USDC|1.5").to_s
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_wire_parse_unchanged
|
|
39
|
+
assert_equal 150_050, Amount.parse("USD|1500.50").atomic
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_parse_dispatches_to_registered_subclass
|
|
43
|
+
use_gold_amount_registry!
|
|
44
|
+
|
|
45
|
+
assert_instance_of GoldAmount, Amount.parse("GOLD|1.0")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_parse_ui_returns_amount_for_registered_class
|
|
49
|
+
assert_instance_of Amount, Amount.parse_ui("1.50", :USD)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_parse_ui_unregistered_symbol_raises_unknown_type_from_new
|
|
53
|
+
assert_raises(Amount::Registry::UnknownType) { Amount.new("1", :NOPE) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_parse_ui_unregistered_symbol_raises_unknown_type
|
|
57
|
+
assert_raises(Amount::Registry::UnknownType) { Amount.parse_ui("1", :NOPE) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_parse_ui_unregistered_symbol_message_matches_new
|
|
61
|
+
new_error = error_from(Amount::Registry::UnknownType) { Amount.new("1", :NOPE) }
|
|
62
|
+
error = error_from(Amount::Registry::UnknownType) { Amount.parse_ui("1", :NOPE) }
|
|
63
|
+
|
|
64
|
+
assert_equal new_error.message, error.message
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_parse_ui_returns_registered_subclass
|
|
68
|
+
use_gold_amount_registry!
|
|
69
|
+
|
|
70
|
+
assert_instance_of GoldAmount, Amount.parse_ui("1.500,5 oz", :GOLD, locale: :de)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_parse_ui_into_registered_subclass_reads_atomic_value
|
|
74
|
+
use_gold_amount_registry!
|
|
75
|
+
|
|
76
|
+
assert_equal 15_005_000, Amount.parse_ui("1.500,5 oz", :GOLD, locale: :de).atomic
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_parse_ui_into_registered_subclass_has_subclass_behavior
|
|
80
|
+
use_gold_amount_registry!
|
|
81
|
+
|
|
82
|
+
assert_equal "24k", Amount.parse_ui("1.500,5 oz", :GOLD, locale: :de).purity
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_parse_ui_into_registered_subclass_rejects_too_many_decimals
|
|
86
|
+
use_gold_amount_registry!
|
|
87
|
+
|
|
88
|
+
assert_raises(Amount::InvalidInput) { Amount.parse_ui("1,00001", :GOLD, locale: :de) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# Replaces the default types with GOLD registered to {GoldAmount}.
|
|
94
|
+
def use_gold_amount_registry!
|
|
95
|
+
Amount.registry.clear!
|
|
96
|
+
Amount.register :GOLD, decimals: 4, display_symbol: "oz", class: GoldAmount
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../test_helper"
|
|
4
|
+
|
|
5
|
+
class AmountRegistrableTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
AmountTestSupport.register_default_types!
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def teardown
|
|
11
|
+
Amount.registry.clear!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_registry_returns_the_same_object_on_repeated_calls
|
|
15
|
+
assert_same Amount.registry, Amount.registry
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_subclass_shares_amount_registry
|
|
19
|
+
assert_same Amount.registry, Class.new(Amount).registry
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_register_adds_a_type
|
|
23
|
+
Amount.register :PTS, decimals: 0
|
|
24
|
+
|
|
25
|
+
assert_equal :PTS, Amount.registry.lookup(:PTS).symbol
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_register_default_rate_stores_the_rate
|
|
29
|
+
Amount.register_default_rate :USD, :USDC, "1"
|
|
30
|
+
|
|
31
|
+
assert_equal BigDecimal("1"), Amount.registry.default_rate(:USD, :USDC)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_with_registry_activates_the_given_registry_inside_the_block
|
|
35
|
+
temporary = Amount::Registry.new
|
|
36
|
+
|
|
37
|
+
assert_same temporary, Amount.with_registry(temporary) { Amount.registry }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_with_registry_restores_the_original_registry_afterwards
|
|
41
|
+
original = Amount.registry
|
|
42
|
+
Amount.with_registry(Amount::Registry.new) { nil }
|
|
43
|
+
|
|
44
|
+
assert_same original, Amount.registry
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_with_registry_restores_the_original_registry_when_the_block_raises
|
|
48
|
+
original = Amount.registry
|
|
49
|
+
error_from(RuntimeError) { Amount.with_registry(Amount::Registry.new) { raise "boom" } }
|
|
50
|
+
|
|
51
|
+
assert_same original, Amount.registry
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_with_registry_returns_the_block_value
|
|
55
|
+
assert_equal :result, Amount.with_registry(Amount::Registry.new) { :result }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_with_registry_removes_outgoing_generated_constructors_inside_the_block
|
|
59
|
+
refute Amount.with_registry(Amount::Registry.new) { Amount.respond_to?(:of_usdc) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_with_registry_restores_outgoing_generated_constructors_afterwards
|
|
63
|
+
Amount.with_registry(Amount::Registry.new) { nil }
|
|
64
|
+
|
|
65
|
+
assert Amount.respond_to?(:of_usdc)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../../test_helper"
|
|
5
|
+
|
|
6
|
+
class AmountSerializableTest < Minitest::Test
|
|
7
|
+
class GoldAmount < Amount
|
|
8
|
+
def purity_estimate
|
|
9
|
+
"24k"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def setup
|
|
14
|
+
AmountTestSupport.register_default_types!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def teardown
|
|
18
|
+
Amount.registry.clear!
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_to_h_and_load_round_trip_for_large_negative_amount
|
|
22
|
+
amount = Amount.new(-(10**30), :USDC, from: :atomic)
|
|
23
|
+
|
|
24
|
+
assert_equal amount, Amount.load(amount.to_h)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_to_h_returns_versioned_string_safe_payload
|
|
28
|
+
assert_equal(
|
|
29
|
+
{ v: 1, atomic: "1500000", symbol: "USDC" },
|
|
30
|
+
Amount.new("1.5", :USDC).to_h
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_load_accepts_legacy_unversioned_payload
|
|
35
|
+
amount = Amount.load(atomic: 1_500_000, symbol: :USDC)
|
|
36
|
+
|
|
37
|
+
assert_equal Amount.new("1.5", :USDC), amount
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_load_without_atomic_raises_invalid_input
|
|
41
|
+
assert_raises(Amount::InvalidInput) { Amount.load(v: 1, symbol: "USDC") }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_load_without_atomic_names_the_missing_key
|
|
45
|
+
assert_match(/missing key: atomic/, error_from(Amount::InvalidInput) { Amount.load(v: 1, symbol: "USDC") }.message)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_load_empty_payload_raises_invalid_input
|
|
49
|
+
assert_raises(Amount::InvalidInput) { Amount.load({}) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_load_empty_payload_reports_missing_key
|
|
53
|
+
assert_match(/missing key/, error_from(Amount::InvalidInput) { Amount.load({}) }.message)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_as_json_returns_compact_string
|
|
57
|
+
assert_equal "USDC|1.50", Amount.new("1.5", :USDC).as_json
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_to_json_returns_quoted_compact_string
|
|
61
|
+
assert_equal '"USDC|1.50"', Amount.new("1.5", :USDC).to_json
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_as_json_works_when_nested_in_hash
|
|
65
|
+
hash = { amount: Amount.new("1.5", :USDC) }
|
|
66
|
+
|
|
67
|
+
assert_equal({ "amount" => "USDC|1.50" }, JSON.parse(hash.to_json))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_load_rejects_unknown_serialization_version
|
|
71
|
+
assert_raises(Amount::InvalidInput) do
|
|
72
|
+
Amount.load(v: 2, atomic: "1500000", symbol: "USDC")
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_load_dispatches_to_registered_subclass
|
|
77
|
+
assert_instance_of GoldAmount, restored_gold
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_load_restores_subclass_behavior
|
|
81
|
+
assert_equal "24k", restored_gold.purity_estimate
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def restored_gold
|
|
87
|
+
register_gold_subclass!
|
|
88
|
+
Amount.load(GoldAmount.new("1", :GOLD).to_h)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Replaces the default registry with GOLD bound to GoldAmount.
|
|
92
|
+
def register_gold_subclass!
|
|
93
|
+
Amount.registry.clear!
|
|
94
|
+
Amount.register :GOLD, decimals: 8, display_symbol: "oz t",
|
|
95
|
+
display_position: :suffix, ui_decimals: 4,
|
|
96
|
+
class: GoldAmount
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bigdecimal"
|
|
4
|
+
require_relative "../test_helper"
|
|
5
|
+
|
|
6
|
+
class AmountDisplayTest < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
AmountTestSupport.register_default_types!
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def teardown
|
|
12
|
+
Amount.registry.clear!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# name => [expected, value, symbol]
|
|
16
|
+
{
|
|
17
|
+
"usdc" => ["1.500000", "1.5", :USDC],
|
|
18
|
+
"sol" => ["1.500000000", "1.5", :SOL],
|
|
19
|
+
"logs" => ["1", 1, :LOGS]
|
|
20
|
+
}.each do |name, (expected, value, symbol)|
|
|
21
|
+
define_method("test_formatted_respects_storage_decimals_for_#{name}") do
|
|
22
|
+
assert_equal expected, Amount.new(value, symbol).formatted
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# name => [expected, value, symbol]
|
|
27
|
+
{
|
|
28
|
+
"usdc" => ["$1.50", "1.5", :USDC],
|
|
29
|
+
"usdc_rounded_down" => ["$1.56", "1.567", :USDC],
|
|
30
|
+
"sol" => ["1.5000 SOL", "1.5", :SOL],
|
|
31
|
+
"logs" => ["1 logs", 1, :LOGS]
|
|
32
|
+
}.each do |name, (expected, value, symbol)|
|
|
33
|
+
define_method("test_ui_respects_ui_decimals_and_position_for_#{name}") do
|
|
34
|
+
assert_equal expected, Amount.new(value, symbol).ui
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_ui_ceil_direction
|
|
39
|
+
assert_equal "$1.57", Amount.new("1.561", :USDC).ui(direction: :ceil)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# name => [expected, value, symbol]
|
|
43
|
+
{
|
|
44
|
+
"usdc" => ["1.50", "1.5", :USDC],
|
|
45
|
+
"sol" => ["1.5000", "1.5", :SOL],
|
|
46
|
+
"logs" => ["1", 1, :LOGS]
|
|
47
|
+
}.each do |name, (expected, value, symbol)|
|
|
48
|
+
define_method("test_ui_decorated_false_omits_the_display_symbol_for_#{name}") do
|
|
49
|
+
assert_equal expected, Amount.new(value, symbol).ui(decorated: false)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_ui_decorated_false_with_ceil_direction
|
|
54
|
+
assert_equal "1.57", Amount.new("1.561", :USDC).ui(direction: :ceil, decorated: false)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# name => [expected, ui options]
|
|
58
|
+
{
|
|
59
|
+
"default_unit" => ["1.5000 oz t", {}],
|
|
60
|
+
"gram" => ["46.65 g", { unit: :gram }],
|
|
61
|
+
"kg" => ["0.04665 kg", { unit: :kg }]
|
|
62
|
+
}.each do |name, (expected, options)|
|
|
63
|
+
define_method("test_display_units_scale_output_in_#{name}") do
|
|
64
|
+
assert_equal expected, Amount.new("1.5", :GOLD).ui(**options)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# name => [expected, ui options]
|
|
69
|
+
{
|
|
70
|
+
"default_unit" => ["1.5000", { decorated: false }],
|
|
71
|
+
"gram" => ["46.65", { unit: :gram, decorated: false }],
|
|
72
|
+
"kg" => ["0.04665", { unit: :kg, decorated: false }]
|
|
73
|
+
}.each do |name, (expected, options)|
|
|
74
|
+
define_method("test_display_units_with_decorated_false_in_#{name}") do
|
|
75
|
+
assert_equal expected, Amount.new("1.5", :GOLD).ui(**options)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_in_unit_returns_raw_bigdecimal
|
|
80
|
+
gold = Amount.new("1.0", :GOLD)
|
|
81
|
+
|
|
82
|
+
assert_in_delta 31.1035, gold.in_unit(:gram).to_f, 0.0001
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_unknown_display_unit_raises
|
|
86
|
+
assert_raises(Amount::Registry::InvalidDisplayUnit) do
|
|
87
|
+
Amount.new("1", :GOLD).ui(unit: :pound)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# name => [expected, value, from]
|
|
92
|
+
{
|
|
93
|
+
"fraction" => ["2.5 TZ", "2.5", nil],
|
|
94
|
+
"whole" => ["1 TZ", "1.0", nil],
|
|
95
|
+
"zero" => ["0 TZ", "0.0", nil],
|
|
96
|
+
"no_trailing_zeros" => ["1.123 TZ", "1.123", nil],
|
|
97
|
+
"small_atomic" => ["0.00005 TZ", 50_000, :atomic]
|
|
98
|
+
}.each do |name, (expected, value, from)|
|
|
99
|
+
define_method("test_trim_zeros_strips_trailing_zeros_for_#{name}") do
|
|
100
|
+
register_trim_zeros_type!
|
|
101
|
+
amount = from ? Amount.new(value, :TZ, from:) : Amount.new(value, :TZ)
|
|
102
|
+
|
|
103
|
+
assert_equal expected, amount.ui
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# name => [expected, value]
|
|
108
|
+
{
|
|
109
|
+
"fraction" => ["$1.50", "1.5"],
|
|
110
|
+
"whole" => ["$1.00", "1.0"]
|
|
111
|
+
}.each do |name, (expected, value)|
|
|
112
|
+
define_method("test_trim_zeros_false_preserves_trailing_zeros_for_#{name}") do
|
|
113
|
+
assert_equal expected, Amount.new(value, :USDC).ui
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# name => [expected, value]
|
|
118
|
+
{
|
|
119
|
+
"fraction" => ["2.5", "2.5"],
|
|
120
|
+
"whole" => ["1", "1.0"]
|
|
121
|
+
}.each do |name, (expected, value)|
|
|
122
|
+
define_method("test_trim_zeros_with_decorated_false_for_#{name}") do
|
|
123
|
+
register_trim_zeros_type!
|
|
124
|
+
|
|
125
|
+
assert_equal expected, Amount.new(value, :TZ).ui(decorated: false)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_trim_zeros_with_display_units
|
|
130
|
+
Amount.register :TZG, decimals: 8, display_symbol: "oz t", display_position: :suffix,
|
|
131
|
+
trim_zeros: true,
|
|
132
|
+
display_units: { gram: { scale: "31.1035", symbol: "g", ui_decimals: 4 } }
|
|
133
|
+
|
|
134
|
+
assert_equal "31.1035 g", Amount.new("1.0", :TZG).ui(unit: :gram)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def test_trim_zeros_display_unit_override
|
|
138
|
+
Amount.register :TZO, decimals: 6, display_symbol: "T", display_position: :suffix,
|
|
139
|
+
trim_zeros: true,
|
|
140
|
+
display_units: { fixed: { scale: 1, symbol: "F", ui_decimals: 2, trim_zeros: false } }
|
|
141
|
+
|
|
142
|
+
assert_equal "1.50 F", Amount.new("1.5", :TZO).ui(unit: :fixed)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def test_trim_zeros_call_site_true_strips_zeros
|
|
146
|
+
assert_equal "1.5 SOL", Amount.new("1.5", :SOL).ui(trim_zeros: true)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def test_trim_zeros_call_site_false_keeps_zeros
|
|
150
|
+
assert_equal "1.0000 SOL", Amount.new("1.0", :SOL).ui(trim_zeros: false)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def test_trim_zeros_call_site_overrides_registry
|
|
154
|
+
Amount.register :TZR, decimals: 6, display_symbol: "T", display_position: :suffix, trim_zeros: true
|
|
155
|
+
|
|
156
|
+
assert_equal "1.500000 T", Amount.new("1.5", :TZR).ui(trim_zeros: false)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def test_in_unit_with_rational_scale
|
|
160
|
+
assert_equal BigDecimal("0.5"), rational_scale_metal.in_unit(:half)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def test_ui_with_rational_scale
|
|
164
|
+
assert_match(/\A0\.50/, rational_scale_metal.ui(unit: :half))
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def register_trim_zeros_type!
|
|
170
|
+
Amount.register :TZ, decimals: 9, display_symbol: "TZ", display_position: :suffix, trim_zeros: true
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def rational_scale_metal
|
|
174
|
+
Amount.registry.clear!
|
|
175
|
+
Amount.register :METAL, decimals: 4, display_symbol: "x", display_position: :suffix,
|
|
176
|
+
ui_decimals: 2,
|
|
177
|
+
display_units: { half: { scale: Rational(1, 2), symbol: "h", ui_decimals: 2 } },
|
|
178
|
+
default_display: :half
|
|
179
|
+
Amount.new("1", :METAL)
|
|
180
|
+
end
|
|
181
|
+
end
|