Skip to content

Commit 8c48997

Browse files
authored
Merge pull request #2131 from woocommerce/issue/1409-currency-formatter-tests-currency-settings
Fix test cases in `CurrencyFormatterTests` from locale, singleton usage, and negative value with RTL
2 parents ecf0b83 + 776b2bc commit 8c48997

File tree

2 files changed

+125
-78
lines changed

2 files changed

+125
-78
lines changed

WooCommerce/Classes/Tools/Currency/CurrencyFormatter.swift

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ public class CurrencyFormatter {
1414
/// Returns a decimal value from a given string.
1515
/// - Parameters:
1616
/// - stringValue: the string received from the API
17+
/// - locale: the locale that the currency string is based on.
1718
///
18-
func convertToDecimal(from stringValue: String) -> NSDecimalNumber? {
19+
func convertToDecimal(from stringValue: String, locale: Locale = .current) -> NSDecimalNumber? {
1920

2021
// NSDecimalNumber use by default the local decimal separator to evaluate a decimal amount.
2122
// We substitute the current decimal separator with the locale decimal separator.
22-
let localeDecimalSeparator = Locale.current.decimalSeparator ?? currencySettings.decimalSeparator
23+
let localeDecimalSeparator = locale.decimalSeparator ?? currencySettings.decimalSeparator
2324
var newStringValue = stringValue.replacingOccurrences(of: ",", with: localeDecimalSeparator)
2425
newStringValue = newStringValue.replacingOccurrences(of: ".", with: localeDecimalSeparator)
2526

@@ -28,7 +29,7 @@ public class CurrencyFormatter {
2829
let unit = currencySettings.symbol(from: currencyCode)
2930
newStringValue = newStringValue.replacingOccurrences(of: unit, with: "")
3031

31-
let decimalValue = NSDecimalNumber(string: newStringValue, locale: Locale.current)
32+
let decimalValue = NSDecimalNumber(string: newStringValue, locale: locale)
3233

3334
guard decimalValue != NSDecimalNumber.notANumber else {
3435
DDLogError("Error: string input is not a number: \(stringValue)")
@@ -72,21 +73,22 @@ public class CurrencyFormatter {
7273

7374
/// Returns a string that displays the amount using all of the specified currency settings
7475
/// - Parameters:
75-
/// - amount: a formatted string, preferably converted using `localize(_:in:with:including:)`.
76+
/// - stringValue: a formatted string, preferably converted using `localize(_:in:with:including:)`.
7677
/// - position: the currency position enum, either right, left, right_space, or left_space.
7778
/// - symbol: the currency symbol as a string, to be used with the amount.
79+
/// - isNegative: whether the value is negative or not.
80+
/// - locale: the locale that is used to format the currency amount string.
7881
///
79-
func formatCurrency(using stringValue: String, at position: CurrencySettings.CurrencyPosition, with symbol: String, isNegative: Bool) -> String {
82+
func formatCurrency(using amount: String,
83+
at position: CurrencySettings.CurrencyPosition,
84+
with symbol: String,
85+
isNegative: Bool,
86+
locale: Locale = .current) -> String {
8087
let space = "\u{00a0}" // unicode equivalent of  
8188
let negative = isNegative ? "-" : ""
8289

8390
// We're relying on the phone's Locale to assist with language direction
84-
let current = Locale.current as NSLocale
85-
let languageCode = current.object(forKey: NSLocale.Key.languageCode) as? String
86-
87-
// Remove all occurences of the minus sign from the string amount.
88-
// We want to position the minus sign manually.
89-
let amount = stringValue.replacingOccurrences(of: "-", with: "")
91+
let languageCode = locale.languageCode
9092

9193
// Detect the language direction
9294
var languageDirection: Locale.LanguageDirection = .unknown
@@ -126,23 +128,25 @@ public class CurrencyFormatter {
126128
/// - Parameters:
127129
/// - amount: a raw string representation of the amount, from the API, with no formatting applied. e.g. "19.87"
128130
/// - currency: a 3-letter country code for currencies that are supported in the API. e.g. "USD"
131+
/// - locale: the locale that is used to format the currency amount string.
129132
///
130-
func formatAmount(_ stringAmount: String, with currency: String = CurrencySettings.shared.currencyCode.rawValue) -> String? {
131-
guard let decimalAmount = convertToDecimal(from: stringAmount) else {
133+
func formatAmount(_ amount: String, with currency: String? = nil, locale: Locale = .current) -> String? {
134+
guard let decimalAmount = convertToDecimal(from: amount, locale: locale) else {
132135
return nil
133136
}
134137

135-
return formatAmount(decimalAmount, with: currency)
138+
return formatAmount(decimalAmount, with: currency ?? currencySettings.currencyCode.rawValue, locale: locale)
136139
}
137140

138141

139142
/// Formats the provided `amount` param into a human readable value and applies the currency option
140143
/// settings for the given currency.
141144
///
142145
/// - Parameters:
143-
/// - amount: a raw string representation of the amount, from the API, with no formatting applied. e.g. "19.87"
146+
/// - stringAmount: a raw string representation of the amount, from the API, with no formatting applied. e.g. "19.87"
144147
/// - currency: a 3-letter country code for currencies that are supported in the API. e.g. "USD"
145148
/// - roundSmallNumbers: if `true`, small numbers are rounded, if `false`, no rounding occurs (defaults to true)
149+
/// - locale: the locale that is used to format the currency amount string.
146150
/// - Returns: a formatted amount string
147151
///
148152
/// For our purposes here, a "small number" is anything in-between -1000 and 1000 (exclusive).
@@ -167,17 +171,21 @@ public class CurrencyFormatter {
167171
/// - 5800199.56 becomes "$5.8m"
168172
///
169173
func formatHumanReadableAmount(_ stringAmount: String,
170-
with currency: String = CurrencySettings.shared.currencyCode.rawValue,
171-
roundSmallNumbers: Bool = true) -> String? {
172-
guard let amount = convertToDecimal(from: stringAmount) else {
174+
with currency: String? = nil,
175+
roundSmallNumbers: Bool = true,
176+
locale: Locale = .current) -> String? {
177+
guard let amount = convertToDecimal(from: stringAmount, locale: locale) else {
178+
assertionFailure("Cannot convert the amount \"\(stringAmount)\" to decimal value with locale \(locale.identifier)")
173179
return nil
174180
}
175181

176-
let humanReadableAmount = amount.humanReadableString(roundSmallNumbers: roundSmallNumbers)
182+
let currency = currency ?? currencySettings.currencyCode.rawValue
183+
184+
let humanReadableAmount = amount.abs().humanReadableString(roundSmallNumbers: roundSmallNumbers)
177185
if humanReadableAmount == amount.stringValue, roundSmallNumbers == false {
178186
// The human readable version of amount is the same as the converted param value which means this is a "small"
179187
// number — format it normally *without* rounding.
180-
return formatAmount(amount, with: currency)
188+
return formatAmount(amount, with: currency, locale: locale)
181189
}
182190

183191
// If we are here, the human readable version of the amount param is a "large" number *OR* a small number but rounding has been requested,
@@ -190,15 +198,18 @@ public class CurrencyFormatter {
190198
return formatCurrency(using: humanReadableAmount,
191199
at: position,
192200
with: symbol,
193-
isNegative: isNegative)
201+
isNegative: isNegative,
202+
locale: locale)
194203
}
195204

196205
/// Applies currency option settings to the amount for the given currency.
197206
/// - Parameters:
198207
/// - amount: a NSDecimalNumber representation of the amount, from the API, with no formatting applied. e.g. "19.87"
199208
/// - currency: a 3-letter country code for currencies that are supported in the API. e.g. "USD"
209+
/// - locale: the locale that is used to format the currency amount string.
200210
///
201-
func formatAmount(_ decimalAmount: NSDecimalNumber, with currency: String = CurrencySettings.shared.currencyCode.rawValue) -> String? {
211+
func formatAmount(_ amount: NSDecimalNumber, with currency: String? = nil, locale: Locale = .current) -> String? {
212+
let currency = currency ?? currencySettings.currencyCode.rawValue
202213
// Get the currency code
203214
let code = CurrencySettings.CurrencyCode(rawValue: currency) ?? currencySettings.currencyCode
204215
// Grab the read-only currency options. These are set by the user in Site > Settings.
@@ -210,7 +221,7 @@ public class CurrencyFormatter {
210221

211222
// Put all the pieces of user preferences on currency formatting together
212223
// and spit out a string that has the formatted amount.
213-
let localized = localize(decimalAmount,
224+
let localized = localize(amount,
214225
with: separator,
215226
in: numberOfDecimals,
216227
including: thousandSeparator)
@@ -224,7 +235,8 @@ public class CurrencyFormatter {
224235
let formattedAmount = formatCurrency(using: localizedAmount,
225236
at: position,
226237
with: symbol,
227-
isNegative: decimalAmount.isNegative())
238+
isNegative: amount.isNegative(),
239+
locale: locale)
228240

229241
return formattedAmount
230242
}

0 commit comments

Comments
 (0)