Use different formatter for different currency (#1432)

* Use different formatter for different currency

* Add functional tests for currency fraction digit format

* Revert "Add functional tests for currency fraction digit format"

This reverts commit bd8aab33847425f4dcfd0d76ce310c918729b2fd.

* Add TestCurrencyFormattingLogic in UnitConverterViewModelUnitTests

* Fix InitializeMultipleConverterTest

* Add comment for a line of code

* Add default case for switch in ConvertToLocalizedString

* Remove trailing decimal
Disable decimal input if maxFractionDigits is 0
Fix input may be blocked after switched active

* Fix: UpdateIsDecimalEnabled should do nothing for non-currency converter

* Remove unnecessary SetValue method

* Add a comment

* Add functional UI Tests for currency converter
Reset currency before tests
Fix: input is blocked after switching to currency with less fractional digits

* Set Priority=0 for currency format related tests

* Truncate digits in display value after switcing
To fix incorrect result after switching currency with less fractional digits
This commit is contained in:
Hongxu Xu
2020-12-03 03:04:22 +08:00
committed by GitHub
parent 6359a14a9b
commit 61d06b2d2f
13 changed files with 432 additions and 43 deletions

View File

@@ -134,7 +134,6 @@ UnitConverterViewModel::UnitConverterViewModel(const shared_ptr<UCM::IUnitConver
m_currencyFormatter->IsGrouped = true;
m_currencyFormatter->Mode = CurrencyFormatterMode::UseCurrencyCode;
m_currencyFormatter->ApplyRoundingForCurrency(RoundingAlgorithm::RoundHalfDown);
m_currencyMaxFractionDigits = m_currencyFormatter->FractionDigits;
auto resourceLoader = AppResourceProvider::GetInstance();
m_localizedValueFromFormat = resourceLoader->GetResourceString(UnitConverterResourceKeys::ValueFromFormat);
@@ -228,7 +227,9 @@ void UnitConverterViewModel::OnUnitChanged(Object ^ parameter)
return;
}
UpdateCurrencyFormatter();
m_model->SetCurrentUnitTypes(UnitFrom->GetModelUnit(), UnitTo->GetModelUnit());
if (m_supplementaryResultsTimer != nullptr)
{
// End timer to show results immediately
@@ -246,7 +247,7 @@ void UnitConverterViewModel::OnSwitchActive(Platform::Object ^ unused)
if (m_relocalizeStringOnSwitch)
{
// clean up any ill-formed strings that were in progress before the switch
ValueFrom = ConvertToLocalizedString(m_valueFromUnlocalized, false);
ValueFrom = ConvertToLocalizedString(m_valueFromUnlocalized, false, CurrencyFormatterParameterFrom);
}
SwitchConversionParameters();
@@ -269,9 +270,11 @@ void UnitConverterViewModel::OnSwitchActive(Platform::Object ^ unused)
m_isInputBlocked = false;
m_model->SwitchActive(m_valueFromUnlocalized);
UpdateIsDecimalEnabled();
}
String ^ UnitConverterViewModel::ConvertToLocalizedString(const std::wstring& stringToLocalize, bool allowPartialStrings)
String ^ UnitConverterViewModel::ConvertToLocalizedString(const std::wstring& stringToLocalize, bool allowPartialStrings, CurrencyFormatterParameter cfp)
{
Platform::String ^ result;
@@ -280,10 +283,33 @@ String ^ UnitConverterViewModel::ConvertToLocalizedString(const std::wstring& st
return result;
}
CurrencyFormatter ^ currencyFormatter;
switch (cfp)
{
case CurrencyFormatterParameter::ForValue1:
currencyFormatter = m_currencyFormatter1;
break;
case CurrencyFormatterParameter::ForValue2:
currencyFormatter = m_currencyFormatter2;
break;
default:
currencyFormatter = m_currencyFormatter;
break;
}
// If unit hasn't been set, currencyFormatter1/2 is nullptr. Fallback to default.
if (currencyFormatter == nullptr)
{
currencyFormatter = m_currencyFormatter;
}
int lastCurrencyFractionDigits = currencyFormatter->FractionDigits;
m_decimalFormatter->IsDecimalPointAlwaysDisplayed = false;
m_decimalFormatter->FractionDigits = 0;
m_currencyFormatter->IsDecimalPointAlwaysDisplayed = false;
m_currencyFormatter->FractionDigits = 0;
currencyFormatter->IsDecimalPointAlwaysDisplayed = false;
currencyFormatter->FractionDigits = 0;
wstring::size_type posOfE = stringToLocalize.find(L'e');
if (posOfE != wstring::npos)
@@ -293,7 +319,8 @@ String ^ UnitConverterViewModel::ConvertToLocalizedString(const std::wstring& st
std::wstring significandStr(stringToLocalize.substr(0, posOfE));
std::wstring exponentStr(stringToLocalize.substr(posOfSign + 1, stringToLocalize.length() - posOfSign));
result += ConvertToLocalizedString(significandStr, allowPartialStrings) + "e" + signOfE + ConvertToLocalizedString(exponentStr, allowPartialStrings);
result += ConvertToLocalizedString(significandStr, allowPartialStrings, cfp) + "e" + signOfE
+ ConvertToLocalizedString(exponentStr, allowPartialStrings, cfp);
}
else
{
@@ -304,7 +331,7 @@ String ^ UnitConverterViewModel::ConvertToLocalizedString(const std::wstring& st
if (hasDecimal)
{
if (allowPartialStrings)
if (allowPartialStrings && lastCurrencyFractionDigits > 0)
{
// allow "in progress" strings, like "3." that occur during the composition of
// a final number. Without this, when typing the three characters in "3.2"
@@ -312,18 +339,18 @@ String ^ UnitConverterViewModel::ConvertToLocalizedString(const std::wstring& st
// typed a post-decimal digit.
m_decimalFormatter->IsDecimalPointAlwaysDisplayed = true;
m_currencyFormatter->IsDecimalPointAlwaysDisplayed = true;
currencyFormatter->IsDecimalPointAlwaysDisplayed = true;
}
// force post-decimal digits so that trailing zeroes entered by the user aren't suddenly cut off.
m_decimalFormatter->FractionDigits = static_cast<int>(stringToLocalize.length() - (posOfDecimal + 1));
m_currencyFormatter->FractionDigits = m_currencyMaxFractionDigits;
currencyFormatter->FractionDigits = lastCurrencyFractionDigits;
}
if (IsCurrencyCurrentCategory)
{
wstring currencyResult = m_currencyFormatter->Format(stod(stringToLocalize))->Data();
wstring currencyCode = m_currencyFormatter->Currency->Data();
wstring currencyResult = currencyFormatter->Format(stod(stringToLocalize))->Data();
wstring currencyCode = currencyFormatter->Currency->Data();
// CurrencyFormatter always includes LangCode or Symbol. Make it include LangCode
// because this includes a non-breaking space. Remove the LangCode.
@@ -381,6 +408,10 @@ String ^ UnitConverterViewModel::ConvertToLocalizedString(const std::wstring& st
}
result = L"-" + result;
}
// restore the original fraction digits
currencyFormatter->FractionDigits = lastCurrencyFractionDigits;
return result;
}
@@ -394,9 +425,9 @@ void UnitConverterViewModel::DisplayPasteError()
void UnitConverterViewModel::UpdateDisplay(const wstring& from, const wstring& to)
{
String ^ fromStr = this->ConvertToLocalizedString(from, true);
String ^ fromStr = this->ConvertToLocalizedString(from, true, CurrencyFormatterParameterFrom);
UpdateInputBlocked(from);
String ^ toStr = this->ConvertToLocalizedString(to, true);
String ^ toStr = this->ConvertToLocalizedString(to, true, CurrencyFormatterParameterTo);
bool updatedValueFrom = ValueFrom != fromStr;
bool updatedValueTo = ValueTo != toStr;
@@ -473,14 +504,14 @@ void UnitConverterViewModel::OnButtonPressed(Platform::Object ^ parameter)
}
static constexpr UCM::Command OPERANDS[] = { UCM::Command::Zero, UCM::Command::One, UCM::Command::Two, UCM::Command::Three, UCM::Command::Four,
UCM::Command::Five, UCM::Command::Six, UCM::Command::Seven, UCM::Command::Eight, UCM::Command::Nine };
if (m_isInputBlocked &&
command != UCM::Command::Clear &&
command != UCM::Command::Backspace)
{
return;
}
m_model->SendCommand(command);
UCM::Command::Five, UCM::Command::Six, UCM::Command::Seven, UCM::Command::Eight, UCM::Command::Nine };
// input should be allowed if user just switches active, because we will clear values in such cases
if (m_isInputBlocked && !m_model->IsSwitchedActive() && command != UCM::Command::Clear && command != UCM::Command::Backspace)
{
return;
}
m_model->SendCommand(command);
TraceLogger::GetInstance()->LogConverterInputReceived(Mode);
}
@@ -755,8 +786,8 @@ void UnitConverterViewModel::RefreshSupplementaryResults()
for (tuple<wstring, UCM::Unit> suggestedValue : m_cachedSuggestedValues)
{
SupplementaryResult ^ result =
ref new SupplementaryResult(this->ConvertToLocalizedString(get<0>(suggestedValue), false), ref new Unit(get<1>(suggestedValue)));
SupplementaryResult ^ result = ref new SupplementaryResult(
this->ConvertToLocalizedString(get<0>(suggestedValue), false, CurrencyFormatterParameter::Default), ref new Unit(get<1>(suggestedValue)));
if (result->IsWhimsical())
{
whimsicals.push_back(result);
@@ -803,10 +834,46 @@ void UnitConverterViewModel::UpdateInputBlocked(_In_ const wstring& currencyInpu
m_isInputBlocked = false;
if (posOfDecimal != wstring::npos && IsCurrencyCurrentCategory)
{
m_isInputBlocked = (posOfDecimal + static_cast<size_t>(m_currencyMaxFractionDigits) + 1 == currencyInput.length());
m_isInputBlocked = (posOfDecimal + static_cast<size_t>(CurrencyFormatterFrom->FractionDigits) + 1 == currencyInput.length());
}
}
std::wstring TruncateFractionDigits(const std::wstring& n, int digitCount)
{
auto i = n.find('.');
if (i == std::wstring::npos)
return n;
size_t actualDigitCount = n.size() - i - 1;
return n.substr(0, n.size() - (actualDigitCount - digitCount));
}
void UnitConverterViewModel::UpdateCurrencyFormatter()
{
if (!IsCurrencyCurrentCategory || m_Unit1->Abbreviation->IsEmpty() || m_Unit2->Abbreviation->IsEmpty())
return;
m_currencyFormatter1 = ref new CurrencyFormatter(m_Unit1->Abbreviation);
m_currencyFormatter1->IsGrouped = true;
m_currencyFormatter1->Mode = CurrencyFormatterMode::UseCurrencyCode;
m_currencyFormatter1->ApplyRoundingForCurrency(RoundingAlgorithm::RoundHalfDown);
m_currencyFormatter2 = ref new CurrencyFormatter(m_Unit2->Abbreviation);
m_currencyFormatter2->IsGrouped = true;
m_currencyFormatter2->Mode = CurrencyFormatterMode::UseCurrencyCode;
m_currencyFormatter2->ApplyRoundingForCurrency(RoundingAlgorithm::RoundHalfDown);
UpdateIsDecimalEnabled();
OnPaste(ref new String(TruncateFractionDigits(m_valueFromUnlocalized, CurrencyFormatterFrom->FractionDigits).data()));
}
void UnitConverterViewModel::UpdateIsDecimalEnabled()
{
if (!IsCurrencyCurrentCategory || CurrencyFormatterFrom == nullptr)
return;
IsDecimalEnabled = CurrencyFormatterFrom->FractionDigits > 0;
}
NumbersAndOperatorsEnum UnitConverterViewModel::MapCharacterToButtonId(const wchar_t ch, bool& canSendNegate)
{
static_assert(NumbersAndOperatorsEnum::Zero < NumbersAndOperatorsEnum::One, "NumbersAndOperatorsEnum order is invalid");
@@ -934,14 +1001,19 @@ void UnitConverterViewModel::OnPaste(String ^ stringToPaste)
}
}
String ^ UnitConverterViewModel::GetLocalizedAutomationName(_In_ String ^ displayvalue, _In_ String ^ unitname, _In_ String ^ format)
String
^ UnitConverterViewModel::GetLocalizedAutomationName(
_In_ String ^ displayvalue,
_In_ String ^ unitname,
_In_ String ^ format,
_In_ CurrencyFormatterParameter cfp)
{
String ^ valueToLocalize = displayvalue;
if (displayvalue == ValueFrom && Utils::IsLastCharacterTarget(m_valueFromUnlocalized, m_decimalSeparator))
{
// Need to compute a second localized value for the automation
// name that does not include the decimal separator.
displayvalue = ConvertToLocalizedString(m_valueFromUnlocalized, false /*allowTrailingDecimal*/);
displayvalue = ConvertToLocalizedString(m_valueFromUnlocalized, false /*allowTrailingDecimal*/, cfp);
format = m_localizedValueFromDecimalFormat;
}
@@ -962,7 +1034,7 @@ void UnitConverterViewModel::UpdateValue1AutomationName()
{
if (Unit1)
{
Value1AutomationName = GetLocalizedAutomationName(Value1, Unit1->AccessibleName, m_localizedValueFromFormat);
Value1AutomationName = GetLocalizedAutomationName(Value1, Unit1->AccessibleName, m_localizedValueFromFormat, CurrencyFormatterParameter::ForValue1);
}
}
@@ -970,7 +1042,7 @@ void UnitConverterViewModel::UpdateValue2AutomationName()
{
if (Unit2)
{
Value2AutomationName = GetLocalizedAutomationName(Value2, Unit2->AccessibleName, m_localizedValueToFormat);
Value2AutomationName = GetLocalizedAutomationName(Value2, Unit2->AccessibleName, m_localizedValueToFormat, CurrencyFormatterParameter::ForValue1);
}
}

View File

@@ -227,8 +227,19 @@ namespace CalculatorApp
void OnCopyCommand(Platform::Object ^ parameter);
void OnPasteCommand(Platform::Object ^ parameter);
enum class CurrencyFormatterParameter
{
Default,
ForValue1,
ForValue2,
};
Platform::String
^ GetLocalizedAutomationName(_In_ Platform::String ^ displayvalue, _In_ Platform::String ^ unitname, _In_ Platform::String ^ format);
^ GetLocalizedAutomationName(
_In_ Platform::String ^ displayvalue,
_In_ Platform::String ^ unitname,
_In_ Platform::String ^ format,
_In_ CurrencyFormatterParameter cfp);
Platform::String
^ GetLocalizedConversionResultStringFormat(
_In_ Platform::String ^ fromValue,
@@ -276,11 +287,13 @@ namespace CalculatorApp
void SupplementaryResultsTimerCancel(Windows::System::Threading::ThreadPoolTimer ^ timer);
void RefreshSupplementaryResults();
void UpdateInputBlocked(_In_ const std::wstring& currencyInput);
void UpdateCurrencyFormatter();
void UpdateIsDecimalEnabled();
bool UnitsAreValid();
void ResetCategory();
void OnButtonPressed(Platform::Object ^ parameter);
Platform::String ^ ConvertToLocalizedString(const std::wstring& stringToLocalize, bool allowPartialStrings);
Platform::String ^ ConvertToLocalizedString(const std::wstring& stringToLocalize, bool allowPartialStrings, CurrencyFormatterParameter cfp);
std::shared_ptr<UnitConversionManager::IUnitConverter> m_model;
wchar_t m_decimalSeparator;
@@ -290,6 +303,34 @@ namespace CalculatorApp
Source,
Target
} m_value1cp;
property CurrencyFormatterParameter CurrencyFormatterParameterFrom
{
CurrencyFormatterParameter get()
{
return m_value1cp == ConversionParameter::Source ? CurrencyFormatterParameter::ForValue1 : CurrencyFormatterParameter::ForValue2;
}
}
property CurrencyFormatterParameter CurrencyFormatterParameterTo
{
CurrencyFormatterParameter get()
{
return m_value1cp == ConversionParameter::Target ? CurrencyFormatterParameter::ForValue1 : CurrencyFormatterParameter::ForValue2;
}
}
property Windows::Globalization::NumberFormatting::CurrencyFormatter^ CurrencyFormatterFrom
{
Windows::Globalization::NumberFormatting::CurrencyFormatter^ get()
{
return m_value1cp == ConversionParameter::Source ? m_currencyFormatter1 : m_currencyFormatter2;
}
}
property Windows::Globalization::NumberFormatting::CurrencyFormatter^ CurrencyFormatterTo
{
Windows::Globalization::NumberFormatting::CurrencyFormatter^ get()
{
return m_value1cp == ConversionParameter::Target ? m_currencyFormatter1 : m_currencyFormatter2;
}
}
property Platform::String^ ValueFrom
{
Platform::String^ get() { return m_value1cp == ConversionParameter::Source ? Value1 : Value2; }
@@ -323,7 +364,8 @@ namespace CalculatorApp
std::mutex m_cacheMutex;
Windows::Globalization::NumberFormatting::DecimalFormatter ^ m_decimalFormatter;
Windows::Globalization::NumberFormatting::CurrencyFormatter ^ m_currencyFormatter;
int m_currencyMaxFractionDigits;
Windows::Globalization::NumberFormatting::CurrencyFormatter ^ m_currencyFormatter1;
Windows::Globalization::NumberFormatting::CurrencyFormatter ^ m_currencyFormatter2;
std::wstring m_valueFromUnlocalized;
std::wstring m_valueToUnlocalized;
bool m_relocalizeStringOnSwitch;