Merge branch 'master' into fix-code-style

This commit is contained in:
seyfer 2019-04-28 12:51:15 +02:00
commit e07f53d053
20 changed files with 1128 additions and 1071 deletions

23
.gitattributes vendored
View file

@ -12,29 +12,6 @@
###############################################################################
*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
*.sln merge=binary
*.csproj merge=binary
*.vbproj merge=binary
*.vcxproj merge=binary
*.vcproj merge=binary
*.dbproj merge=binary
*.fsproj merge=binary
*.lsproj merge=binary
*.wixproj merge=binary
*.modelproj merge=binary
*.sqlproj merge=binary
*.wwaproj merge=binary
###############################################################################
# behavior for image files
#

1
.gitignore vendored
View file

@ -289,6 +289,7 @@ __pycache__/
# Calculator specific
Generated Files/
src/GraphControl/GraphingImplOverrides.props
!/build/config/TRexDefs/**
!src/Calculator/TemporaryKey.pfx
!src/CalculatorUnitTests/CalculatorUnitTests_TemporaryKey.pfx

View file

@ -16,6 +16,9 @@ Calculator ships regularly with new features and bug fixes. You can get the late
- Calculation history and memory capabilities.
- Conversion between many units of measurement.
- Currency conversion based on data retrieved from [Bing](https://www.bing.com).
- [Infinite precision](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) for basic
arithmetic operations (addition, subtraction, multiplication, division) so that calculations
never lose precision.
## Getting started
Prerequisites:

View file

@ -1,5 +1,5 @@
<SignConfigXML>
<job platform="" configuration="" certSubject="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" jobname="EngFunSimpleSign" approvers="gstolt;vigarg">
<job platform="" configuration="" certSubject="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" jobname="EngFunSimpleSign" approvers="">
<file src="__INPATHROOT__\Microsoft.WindowsCalculator_8wekyb3d8bbwe.appxbundle" signType="136020001" dest="__OUTPATHROOT__\Microsoft.WindowsCalculator_8wekyb3d8bbwe.appxbundle" />
</job>
</SignConfigXML>

View file

@ -33,6 +33,10 @@ jobs:
platform: ARM64
condition: not(eq(variables['Build.Reason'], 'PullRequest'))
- template: ./templates/run-ui-tests.yaml
parameters:
platform: x64
- template: ./templates/run-unit-tests.yaml
parameters:
platform: x64

View file

@ -30,7 +30,7 @@ steps:
inputs:
solution: src/Calculator.sln
vsVersion: 15.0
msbuildArgs: /bl:$(Build.BinariesDirectory)\$(BuildConfiguration)\$(BuildPlatform)\Calculator.binlog /p:OutDir=$(Build.BinariesDirectory)\$(BuildConfiguration)\$(BuildPlatform)\ /p:GenerateProjectSpecificOutputFolder=true /p:AppVersion=$(Build.BuildNumber) ${{ parameters.extraMsBuildArgs }}
msbuildArgs: /bl:$(Build.BinariesDirectory)\$(BuildConfiguration)\$(BuildPlatform)\Calculator.binlog /p:OutDir=$(Build.BinariesDirectory)\$(BuildConfiguration)\$(BuildPlatform)\ /p:GenerateProjectSpecificOutputFolder=true /p:AppVersion=$(Build.BuildNumber) /t:Publish /p:PublishDir=$(Build.BinariesDirectory)\$(BuildConfiguration)\$(BuildPlatform)\publish\ ${{ parameters.extraMsBuildArgs }}
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
clean: true

View file

@ -1,5 +1,5 @@
# This template contains a job which builds artifacts needed to release the app to the store and to
# Windows using Microsoft-internal systems. It relies Microsoft-internal resources and will not
# Windows using Microsoft-internal systems. It relies on Microsoft-internal resources and will not
# work outside of Microsoft.
# Specifically, this job:
# - Signs the bundle using a secure system. If you want to build your own, use SignTool following

View file

@ -0,0 +1,52 @@
# This template contains jobs to run UI tests using WinAppDriver.
parameters:
platform: ''
jobs:
- job: UITests${{ parameters.platform }}
displayName: UITests ${{ parameters.platform }}
dependsOn: Build${{ parameters.platform }}
condition: succeeded()
pool:
vmImage: windows-2019
variables:
skipComponentGovernanceDetection: true
steps:
- checkout: none
- powershell: Set-DisplayResolution -Width 1920 -Height 1080 -Force
displayName: Set resolution to 1920x1080
continueOnError: true
- task: DownloadBuildArtifacts@0
displayName: Download AppxBundle and CalculatorUITests
inputs:
artifactName: drop
itemPattern: |
drop/Release/${{ parameters.platform }}/Calculator/AppPackages/**
drop/Release/${{ parameters.platform }}/publish/**
- task: PowerShell@2
displayName: Install certificate
inputs:
filePath: $(Build.ArtifactStagingDirectory)/drop/Release/${{ parameters.platform }}/Calculator/AppPackages/Calculator_$(Build.BuildNumber)_Test/Add-AppDevPackage.ps1
arguments: -CertificatePath $(Build.ArtifactStagingDirectory)/drop/Release/${{ parameters.platform }}/Calculator/AppPackages/Calculator_$(Build.BuildNumber)_Test/Calculator_$(Build.BuildNumber)_${{ parameters.platform }}.cer -Force
- task: PowerShell@2
displayName: Install app
inputs:
filePath: $(Build.ArtifactStagingDirectory)/drop/Release/${{ parameters.platform }}/Calculator/AppPackages/Calculator_$(Build.BuildNumber)_Test/Add-AppDevPackage.ps1
arguments: -Force
- powershell: Start-Process -FilePath "C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe" -Verb RunAs
displayName: Start WinAppDriver
- task: VSTest@2
displayName: Run CalculatorUITests
inputs:
testAssemblyVer2: $(Build.ArtifactStagingDirectory)/drop/Release/${{ parameters.platform }}/publish/CalculatorUITests.dll
vsTestVersion: 16.0
runSettingsFile: $(Build.ArtifactStagingDirectory)/drop/Release/${{ parameters.platform }}/publish/CalculatorUITests.runsettings
platform: ${{ parameters.platform }}
configuration: Release

View file

@ -153,7 +153,9 @@ The CalcEngine contains the logic for interpreting and performing operations acc
### RatPack
The RatPack (short for Rational Pack) is the core of the Calculator model and contains the logic for performing its mathematical operations. The interface to this layer is defined in [ratpak.h][ratpak.h].
The RatPack (short for Rational Pack) is the core of the Calculator model and contains the logic for
performing its mathematical operations (using [infinite precision][Infinite Precision] arithmetic
instead of regular floating point arithmetic). The interface to this layer is defined in [ratpak.h][ratpak.h].
[References]:####################################################################################################

View file

@ -15,11 +15,15 @@ using namespace Windows::Foundation;
using namespace Windows::System;
using namespace Windows::ApplicationModel::DataTransfer;
unsigned long long maxOperandNumber;
String^ CopyPasteManager::supportedFormats[] =
{
StandardDataFormats::Text
};
String ^ CopyPasteManager::supportedFormats[] = { StandardDataFormats::Text };
static constexpr wstring_view c_validCharacterSet{ L"0123456789()+-*/.abcdefABCDEF" };
constexpr wstring_view c_validCharacterSet{ L"0123456789()+-*/.abcdefABCDEF" };
// The below values can not be "constexpr"-ed,
// as both wstring_view and wchar[] can not be concatenated
// [\s\x85] means white-space characters
static const wstring c_wspc = L"[\\s\\x85]*";
static const wstring c_wspcLParens = c_wspc + L"[(]*" + c_wspc;
@ -36,26 +40,43 @@ static const wstring c_binProgrammerChars = L"[0-1]+((_|'|`)[0-1]+)*";
static const wstring c_uIntSuffixes = L"[uU]?[lL]{0,2}";
// RegEx Patterns used by various modes
static const array<wregex, 1> standardModePatterns = { wregex(c_wspc + c_signedDecFloat + c_wspc) };
static const array<wregex, 2> scientificModePatterns = { wregex(L"(" + c_wspc + L"[-+]?)|(" + c_wspcLParenSigned + L")" + c_signedDecFloat + c_wspcRParens),
wregex(L"(" + c_wspc + L"[-+]?)|(" + c_wspcLParenSigned + L")" + c_signedDecFloat
+ L"[e]([+]|[-])+\\d+" + c_wspcRParens) };
static const array<array<wregex, 5>, 4> programmerModePatterns = {
{ // Hex numbers like 5F, 4A0C, 0xa9, 0xFFull, 47CDh
{ wregex(c_wspcLParens + L"(0[xX])?" + c_hexProgrammerChars + c_uIntSuffixes + c_wspcRParens),
wregex(c_wspcLParens + c_hexProgrammerChars + L"[hH]?" + c_wspcRParens) },
// Decimal numbers like -145, 145, 0n145, 123ull etc
{ wregex(c_wspcLParens + L"[-+]?" + c_decProgrammerChars + L"[lL]{0,2}" + c_wspcRParens),
wregex(c_wspcLParens + L"(0[nN])?" + c_decProgrammerChars + c_uIntSuffixes + c_wspcRParens) },
// Octal numbers like 06, 010, 0t77, 0o77, 077ull etc
{ wregex(c_wspcLParens + L"(0[otOT])?" + c_octProgrammerChars + c_uIntSuffixes + c_wspcRParens) },
// Binary numbers like 011010110, 0010110, 10101001, 1001b, 0b1001, 0y1001, 0b1001ull
{ wregex(c_wspcLParens + L"(0[byBY])?" + c_binProgrammerChars + c_uIntSuffixes + c_wspcRParens),
wregex(c_wspcLParens + c_binProgrammerChars + L"[bB]?" + c_wspcRParens) } }
static const array<wregex, 1> standardModePatterns =
{
wregex(c_wspc + c_signedDecFloat + c_wspc)
};
static const array<wregex, 2> scientificModePatterns =
{
wregex(L"(" + c_wspc + L"[-+]?)|(" + c_wspcLParenSigned + L")" + c_signedDecFloat + c_wspcRParens),
wregex(L"(" + c_wspc + L"[-+]?)|(" + c_wspcLParenSigned + L")" + c_signedDecFloat + L"[e]([+]|[-])+\\d+" + c_wspcRParens)
};
static const array<array<wregex, 5>, 4> programmerModePatterns =
{ {
// Hex numbers like 5F, 4A0C, 0xa9, 0xFFull, 47CDh
{
wregex(c_wspcLParens + L"(0[xX])?" + c_hexProgrammerChars + c_uIntSuffixes + c_wspcRParens),
wregex(c_wspcLParens + c_hexProgrammerChars + L"[hH]?" + c_wspcRParens)
},
// Decimal numbers like -145, 145, 0n145, 123ull etc
{
wregex(c_wspcLParens + L"[-+]?" + c_decProgrammerChars + L"[lL]{0,2}" +c_wspcRParens),
wregex(c_wspcLParens + L"(0[nN])?" + c_decProgrammerChars + c_uIntSuffixes + c_wspcRParens)
},
// Octal numbers like 06, 010, 0t77, 0o77, 077ull etc
{
wregex(c_wspcLParens + L"(0[otOT])?" + c_octProgrammerChars + c_uIntSuffixes + c_wspcRParens)
},
// Binary numbers like 011010110, 0010110, 10101001, 1001b, 0b1001, 0y1001, 0b1001ull
{
wregex(c_wspcLParens + L"(0[byBY])?" + c_binProgrammerChars + c_uIntSuffixes + c_wspcRParens),
wregex(c_wspcLParens + c_binProgrammerChars + L"[bB]?" + c_wspcRParens)
}
} };
static const array<wregex, 1> unitConverterPatterns =
{
wregex(c_wspc + L"[-+]?\\d*[.]?\\d*" + c_wspc)
};
static const array<wregex, 1> unitConverterPatterns = { wregex(c_wspc + L"[-+]?\\d*[.]?\\d*" + c_wspc) };
void CopyPasteManager::CopyToClipboard(String ^ stringToCopy)
void CopyPasteManager::CopyToClipboard(String^ stringToCopy)
{
// Copy the string to the clipboard
auto dataPackage = ref new DataPackage();
@ -63,7 +84,7 @@ void CopyPasteManager::CopyToClipboard(String ^ stringToCopy)
Clipboard::SetContent(dataPackage);
}
task<String ^> CopyPasteManager::GetStringToPaste(ViewMode mode, CategoryGroupType modeType, int programmerNumberBase, int bitLengthType)
task<String^> CopyPasteManager::GetStringToPaste(ViewMode mode, CategoryGroupType modeType, int programmerNumberBase, int bitLengthType)
{
// Retrieve the text in the clipboard
auto dataPackageView = Clipboard::GetContent();
@ -74,36 +95,35 @@ task<String ^> CopyPasteManager::GetStringToPaste(ViewMode mode, CategoryGroupTy
//-- add support to allow pasting for expressions like 1.3e12(as of now we allow 1.3e+12)
return create_task((dataPackageView->GetTextAsync(::StandardDataFormats::Text)))
.then([mode, modeType, programmerNumberBase,
bitLengthType](String ^ pastedText) { return ValidatePasteExpression(pastedText, mode, modeType, programmerNumberBase, bitLengthType); },
task_continuation_context::use_arbitrary());
.then([mode, modeType, programmerNumberBase, bitLengthType](String^ pastedText)
{
return ValidatePasteExpression(pastedText, mode, modeType, programmerNumberBase, bitLengthType);
}
, task_continuation_context::use_arbitrary());
}
int CopyPasteManager::ClipboardTextFormat()
{
int result = -1;
auto dataPackageView = Clipboard::GetContent();
const auto dataPackageView = Clipboard::GetContent();
for (int i = 0; i < RTL_NUMBER_OF(supportedFormats); i++)
{
if (dataPackageView->Contains(supportedFormats[i]))
{
result = i;
break;
return i;
}
}
return result;
return -1;
}
String ^ CopyPasteManager::ValidatePasteExpression(String ^ pastedText, ViewMode mode, int programmerNumberBase, int bitLengthType)
String^ CopyPasteManager::ValidatePasteExpression(String^ pastedText, ViewMode mode, int programmerNumberBase, int bitLengthType)
{
return CopyPasteManager::ValidatePasteExpression(pastedText, mode, NavCategory::GetGroupType(mode), programmerNumberBase, bitLengthType);
}
// return "NoOp" if pastedText is invalid else return pastedText
String ^ CopyPasteManager::ValidatePasteExpression(String ^ pastedText, ViewMode mode, CategoryGroupType modeType, int programmerNumberBase, int bitLengthType)
String^ CopyPasteManager::ValidatePasteExpression(String^ pastedText, ViewMode mode, CategoryGroupType modeType, int programmerNumberBase, int bitLengthType)
{
if (pastedText->Length() > MaxPasteableLength)
{
@ -115,7 +135,7 @@ String ^ CopyPasteManager::ValidatePasteExpression(String ^ pastedText, ViewMode
wstring pasteExpression = pastedText->Data();
// Get english translated expression
String ^ englishString = LocalizationSettings::GetInstance().GetEnglishValueFromLocalizedDigits(pasteExpression);
String^ englishString = LocalizationSettings::GetInstance().GetEnglishValueFromLocalizedDigits(pasteExpression);
// Removing the spaces, comma separator from the pasteExpression to allow pasting of expressions like 1 + 2+1,333
pasteExpression = RemoveUnwantedCharsFromWstring(englishString->Data());
@ -203,8 +223,7 @@ vector<wstring> CopyPasteManager::ExtractOperands(const wstring& pasteExpression
if ((pasteExpression.at(i) == L'+') || (pasteExpression.at(i) == L'-'))
{
// don't break the expression into operands if the encountered character corresponds to sign command(+-)
if (isPreviousOpenParen || startOfExpression || isPreviousOperator
|| ((mode != ViewMode::Programmer) && !((i != 0) && (pasteExpression.at(i - 1) != L'e'))))
if (isPreviousOpenParen || startOfExpression || isPreviousOperator || ((mode != ViewMode::Programmer) && !((i != 0) && (pasteExpression.at(i - 1) != L'e'))))
{
isPreviousOperator = false;
continue;
@ -247,13 +266,8 @@ bool CopyPasteManager::ExpressionRegExMatch(vector<wstring> operands, ViewMode m
return false;
}
bool expMatched = true;
vector<wregex> patterns{};
pair<size_t, uint64_t> operandLimits = GetMaxOperandLengthAndValue(mode, modeType, programmerNumberBase, bitLengthType);
size_t maxOperandLength = operandLimits.first;
uint64_t maxOperandValue = operandLimits.second;
if (mode == ViewMode::Standard)
{
patterns.assign(standardModePatterns.begin(), standardModePatterns.end());
@ -271,11 +285,14 @@ bool CopyPasteManager::ExpressionRegExMatch(vector<wstring> operands, ViewMode m
patterns.assign(unitConverterPatterns.begin(), unitConverterPatterns.end());
}
for (const wstring& operand : operands)
const auto [maxOperandLength, maxOperandValue] = GetMaxOperandLengthAndValue(mode, modeType, programmerNumberBase, bitLengthType);
bool expMatched = true;
for (const auto& operand : operands)
{
// Each operand only needs to match one of the available patterns.
bool operandMatched = false;
for (const wregex& pattern : patterns)
for (const auto& pattern : patterns)
{
operandMatched = operandMatched || regex_match(operand, pattern);
}
@ -284,7 +301,7 @@ bool CopyPasteManager::ExpressionRegExMatch(vector<wstring> operands, ViewMode m
{
// Remove characters that are valid in the expression but we do not want to include in length calculations
// or which will break conversion from string-to-ULL.
wstring operandValue = SanitizeOperand(operand);
const wstring operandValue = SanitizeOperand(operand);
// If an operand exceeds the maximum length allowed, break and return.
if (OperandLength(operandValue, mode, modeType, programmerNumberBase) > maxOperandLength)
@ -320,16 +337,16 @@ bool CopyPasteManager::ExpressionRegExMatch(vector<wstring> operands, ViewMode m
pair<size_t, uint64_t> CopyPasteManager::GetMaxOperandLengthAndValue(ViewMode mode, CategoryGroupType modeType, int programmerNumberBase, int bitLengthType)
{
size_t maxLength = 0;
uint64_t maxValue = 0;
constexpr size_t defaultMaxOperandLength = 0;
constexpr uint64_t defaultMaxValue = 0;
if (mode == ViewMode::Standard)
{
maxLength = MaxStandardOperandLength;
return make_pair(MaxStandardOperandLength, defaultMaxValue);
}
else if (mode == ViewMode::Scientific)
{
maxLength = MaxScientificOperandLength;
return make_pair(MaxScientificOperandLength, defaultMaxValue);
}
else if (mode == ViewMode::Programmer)
{
@ -369,15 +386,17 @@ pair<size_t, uint64_t> CopyPasteManager::GetMaxOperandLengthAndValue(ViewMode mo
unsigned int signBit = (programmerNumberBase == DecBase) ? 1 : 0;
maxLength = (size_t)ceil((bitLength - signBit) / bitsPerDigit);
maxValue = UINT64_MAX >> (MaxProgrammerBitLength - (bitLength - signBit));
const auto maxLength = static_cast<size_t>(ceil((bitLength - signBit) / bitsPerDigit));
const uint64_t maxValue = UINT64_MAX >> (MaxProgrammerBitLength - (bitLength - signBit));
return make_pair(maxLength, maxValue);
}
else if (modeType == CategoryGroupType::Converter)
{
maxLength = MaxConverterInputLength;
return make_pair(MaxConverterInputLength, defaultMaxValue);
}
return make_pair(maxLength, maxValue);
return make_pair(defaultMaxOperandLength, defaultMaxValue);
}
wstring CopyPasteManager::SanitizeOperand(const wstring& operand)
@ -396,8 +415,7 @@ bool CopyPasteManager::TryOperandToULL(const wstring& operand, int numberBase, u
return false;
}
// Default to base10
int intBase = 10;
int intBase;
switch (numberBase)
{
case HexBase:
@ -409,6 +427,7 @@ bool CopyPasteManager::TryOperandToULL(const wstring& operand, int numberBase, u
case BinBase:
intBase = 2;
break;
default:
case DecBase:
intBase = 10;
break;
@ -420,11 +439,11 @@ bool CopyPasteManager::TryOperandToULL(const wstring& operand, int numberBase, u
result = stoull(operand, &size, intBase);
return true;
}
catch (invalid_argument)
catch (const invalid_argument&)
{
// Do nothing
}
catch (out_of_range)
catch (const out_of_range&)
{
// Do nothing
}
@ -432,35 +451,28 @@ bool CopyPasteManager::TryOperandToULL(const wstring& operand, int numberBase, u
return false;
}
size_t CopyPasteManager::OperandLength(wstring operand, ViewMode mode, CategoryGroupType modeType, int programmerNumberBase)
{
size_t len = 0;
if (mode == ViewMode::Standard || mode == ViewMode::Scientific)
{
len = StandardScientificOperandLength(operand);
}
else if (mode == ViewMode::Programmer)
{
len = ProgrammerOperandLength(operand, programmerNumberBase);
}
else if (modeType == CategoryGroupType::Converter)
{
len = operand.length();
size_t CopyPasteManager::OperandLength(const wstring& operand, ViewMode mode, CategoryGroupType modeType, int programmerNumberBase)
{
if (modeType == CategoryGroupType::Converter) {
return operand.length();
}
return len;
switch(mode) {
case ViewMode::Standard:
case ViewMode::Scientific:
return StandardScientificOperandLength(operand);
case ViewMode::Programmer:
return ProgrammerOperandLength(operand, programmerNumberBase);
default:
return 0;
}
}
size_t CopyPasteManager::StandardScientificOperandLength(wstring operand)
{
bool hasDecimal = false;
for (size_t i = 0; i < operand.length(); i++)
{
if (operand[i] == L'.')
{
hasDecimal = true;
}
}
size_t CopyPasteManager::StandardScientificOperandLength(const wstring& operand)
{
const bool hasDecimal = operand.find('.') != wstring::npos;
if (hasDecimal)
{
@ -482,8 +494,7 @@ size_t CopyPasteManager::StandardScientificOperandLength(wstring operand)
size_t CopyPasteManager::ProgrammerOperandLength(const wstring& operand, int numberBase)
{
size_t len = operand.length();
vector<wstring> prefixes{};
vector<wstring> suffixes{};
switch (numberBase)
@ -504,7 +515,7 @@ size_t CopyPasteManager::ProgrammerOperandLength(const wstring& operand, int num
break;
default:
// No defined prefixes/suffixes
break;
return 0;
}
// UInt suffixes are common across all modes
@ -514,9 +525,11 @@ size_t CopyPasteManager::ProgrammerOperandLength(const wstring& operand, int num
wstring operandUpper = operand;
transform(operandUpper.begin(), operandUpper.end(), operandUpper.begin(), towupper);
size_t len = operand.length();
// Detect if there is a suffix and subtract its length
// Check suffixes first to allow e.g. "0b" to result in length 1 (value 0), rather than length 0 (no value).
for (const wstring& suffix : suffixes)
for (const auto& suffix : suffixes)
{
if (len < suffix.length())
{
@ -531,7 +544,7 @@ size_t CopyPasteManager::ProgrammerOperandLength(const wstring& operand, int num
}
// Detect if there is a prefix and subtract its length
for (const wstring& prefix : prefixes)
for (const auto& prefix : prefixes)
{
if (len < prefix.length())
{

View file

@ -13,21 +13,20 @@ namespace CalculatorUnitTests
namespace CalculatorApp
{
#define QwordType 1
#define DwordType 2
#define WordType 3
#define ByteType 4
#define HexBase 5
#define DecBase 6
#define OctBase 7
#define BinBase 8
inline constexpr auto QwordType = 1;
inline constexpr auto DwordType = 2;
inline constexpr auto WordType = 3;
inline constexpr auto ByteType = 4;
inline constexpr auto HexBase = 5;
inline constexpr auto DecBase = 6;
inline constexpr auto OctBase = 7;
inline constexpr auto BinBase = 8;
class CopyPasteManager
{
public:
static void CopyToClipboard(Platform::String ^ stringToCopy);
static concurrency::task<Platform::String ^> GetStringToPaste(CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType,
int programmerNumberBase = -1, int bitLengthType = -1);
static void CopyToClipboard(Platform::String^ stringToCopy);
static concurrency::task<Platform::String^> GetStringToPaste(CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType, int programmerNumberBase = -1, int bitLengthType = -1);
static bool HasStringToPaste()
{
return ClipboardTextFormat() >= 0;
@ -37,24 +36,26 @@ namespace CalculatorApp
private:
static int ClipboardTextFormat();
static Platform::String
^ ValidatePasteExpression(Platform::String ^ pastedText, CalculatorApp::Common::ViewMode mode, int programmerNumberBase, int bitLengthType);
static Platform::String
^ ValidatePasteExpression(Platform::String ^ pastedText, CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType,
int programmerNumberBase, int bitLengthType);
static Platform::String^ ValidatePasteExpression(
Platform::String^ pastedText,
CalculatorApp::Common::ViewMode mode,
int programmerNumberBase,
int bitLengthType);
static Platform::String^ ValidatePasteExpression(
Platform::String^ pastedText,
CalculatorApp::Common::ViewMode mode,
CalculatorApp::Common::CategoryGroupType modeType,
int programmerNumberBase,
int bitLengthType);
static std::vector<std::wstring> ExtractOperands(const std::wstring& pasteExpression, CalculatorApp::Common::ViewMode mode,
int programmerNumberBase = -1, int bitLengthType = -1);
static bool ExpressionRegExMatch(std::vector<std::wstring> operands, CalculatorApp::Common::ViewMode mode,
CalculatorApp::Common::CategoryGroupType modeType, int programmerNumberBase = -1, int bitLengthType = -1);
static std::vector<std::wstring> ExtractOperands(const std::wstring& pasteExpression, CalculatorApp::Common::ViewMode mode, int programmerNumberBase = -1, int bitLengthType = -1);
static bool ExpressionRegExMatch(std::vector<std::wstring> operands, CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType, int programmerNumberBase = -1, int bitLengthType = -1);
static std::pair<size_t, uint64_t> GetMaxOperandLengthAndValue(CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType,
int programmerNumberBase = -1, int bitLengthType = -1);
static std::pair<size_t, uint64_t> GetMaxOperandLengthAndValue(CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType, int programmerNumberBase = -1, int bitLengthType = -1);
static std::wstring SanitizeOperand(const std::wstring& operand);
static bool TryOperandToULL(const std::wstring& operand, int numberBase, unsigned long long int& result);
static size_t OperandLength(std::wstring operand, CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType,
int programmerNumberBase = -1);
static size_t StandardScientificOperandLength(std::wstring operand);
static size_t OperandLength(const std::wstring& operand, CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType, int programmerNumberBase = -1);
static size_t StandardScientificOperandLength(const std::wstring& operand);
static size_t ProgrammerOperandLength(const std::wstring& operand, int numberBase);
static std::wstring RemoveUnwantedCharsFromWstring(const std::wstring& input);
@ -66,7 +67,7 @@ namespace CalculatorApp
static constexpr size_t MaxExponentLength = 4;
static constexpr size_t MaxProgrammerBitLength = 64;
static Platform::String ^ supportedFormats[];
static Platform::String^ supportedFormats[];
friend class CalculatorUnitTests::CopyPasteManagerTest;
};

View file

@ -20,13 +20,27 @@ DateCalculationEngine::DateCalculationEngine(_In_ String ^ calendarIdentifier)
// Returns: True if function succeeds to calculate the date else returns False
bool DateCalculationEngine::AddDuration(_In_ DateTime startDate, _In_ const DateDifference& duration, _Out_ DateTime* endDate)
{
auto currentCalendarSystem = m_calendar->GetCalendarSystem();
try
{
m_calendar->SetDateTime(startDate);
if (duration.year != 0)
{
// The Japanese Era system can have multiple year partitions within the same year.
// For example, April 30, 2019 is denoted April 30, Heisei 31; May 1, 2019 is denoted as May 1, Reiwa 1.
// The Calendar treats Heisei 31 and Reiwa 1 as separate years, which results in some unexpected behaviors where subtracting a year from Reiwa 1 results in a date in Heisei 31.
// To provide the expected result across era boundaries, we first convert the Japanese era system to a Gregorian system, do date math, and then convert back to the Japanese era system.
// This works because the Japanese era system maintains the same year/month boundaries and durations as the Gregorian system and is only different in display value.
if (currentCalendarSystem == CalendarIdentifiers::Japanese)
{
m_calendar->ChangeCalendarSystem(CalendarIdentifiers::Gregorian);
}
m_calendar->AddYears(duration.year);
m_calendar->ChangeCalendarSystem(currentCalendarSystem);
}
if (duration.month != 0)
{
@ -41,6 +55,9 @@ bool DateCalculationEngine::AddDuration(_In_ DateTime startDate, _In_ const Date
}
catch (Platform::InvalidArgumentException ^ ex)
{
// ensure that we revert to the correct calendar system
m_calendar->ChangeCalendarSystem(currentCalendarSystem);
// Do nothing
return false;
}
@ -52,6 +69,8 @@ bool DateCalculationEngine::AddDuration(_In_ DateTime startDate, _In_ const Date
// Returns: True if function succeeds to calculate the date else returns False
bool DateCalculationEngine::SubtractDuration(_In_ DateTime startDate, _In_ const DateDifference& duration, _Out_ DateTime* endDate)
{
auto currentCalendarSystem = m_calendar->GetCalendarSystem();
// For Subtract the Algorithm is different than Add. Here the smaller units are subtracted first
// and then the larger units.
try
@ -68,13 +87,28 @@ bool DateCalculationEngine::SubtractDuration(_In_ DateTime startDate, _In_ const
}
if (duration.year != 0)
{
// The Japanese Era system can have multiple year partitions within the same year.
// For example, April 30, 2019 is denoted April 30, Heisei 31; May 1, 2019 is denoted as May 1, Reiwa 1.
// The Calendar treats Heisei 31 and Reiwa 1 as separate years, which results in some unexpected behaviors where subtracting a year from Reiwa 1 results in a date in Heisei 31.
// To provide the expected result across era boundaries, we first convert the Japanese era system to a Gregorian system, do date math, and then convert back to the Japanese era system.
// This works because the Japanese era system maintains the same year/month boundaries and durations as the Gregorian system and is only different in display value.
if (currentCalendarSystem == CalendarIdentifiers::Japanese)
{
m_calendar->ChangeCalendarSystem(CalendarIdentifiers::Gregorian);
}
m_calendar->AddYears(-duration.year);
m_calendar->ChangeCalendarSystem(currentCalendarSystem);
}
*endDate = m_calendar->GetDateTime();
}
catch (Platform::InvalidArgumentException ^ ex)
{
// ensure that we revert to the correct calendar system
m_calendar->ChangeCalendarSystem(currentCalendarSystem);
// Do nothing
return false;
}

View file

@ -782,6 +782,12 @@ void StandardCalculatorViewModel::OnPaste(String ^ pastedString, ViewMode mode)
NumbersAndOperatorsEnum mappedNumOp = MapCharacterToButtonId(*it, canSendNegate);
if (mappedNumOp == NumbersAndOperatorsEnum::None)
{
++it;
continue;
}
if (isFirstLegalChar || isPreviousOperator)
{
isFirstLegalChar = false;
@ -803,74 +809,71 @@ void StandardCalculatorViewModel::OnPaste(String ^ pastedString, ViewMode mode)
}
}
if (mappedNumOp != NumbersAndOperatorsEnum::None)
switch (mappedNumOp)
{
switch (mappedNumOp)
// Opening parenthesis starts a new expression and pushes negation state onto the stack
case NumbersAndOperatorsEnum::OpenParenthesis:
negateStack.push_back(sendNegate);
sendNegate = false;
break;
// Closing parenthesis pops the negation state off the stack and sends it down to the calc engine
case NumbersAndOperatorsEnum::CloseParenthesis:
if (!negateStack.empty())
{
// Opening parenthesis starts a new expression and pushes negation state onto the stack
case NumbersAndOperatorsEnum::OpenParenthesis:
negateStack.push_back(sendNegate);
sendNegate = false;
break;
// Closing parenthesis pops the negation state off the stack and sends it down to the calc engine
case NumbersAndOperatorsEnum::CloseParenthesis:
if (!negateStack.empty())
{
sendNegate = negateStack.back();
negateStack.pop_back();
canSendNegate = true;
}
else
{
// Don't send a closing parenthesis if a matching opening parenthesis hasn't been sent already
sendCommand = false;
}
break;
case NumbersAndOperatorsEnum::Zero:
case NumbersAndOperatorsEnum::One:
case NumbersAndOperatorsEnum::Two:
case NumbersAndOperatorsEnum::Three:
case NumbersAndOperatorsEnum::Four:
case NumbersAndOperatorsEnum::Five:
case NumbersAndOperatorsEnum::Six:
case NumbersAndOperatorsEnum::Seven:
case NumbersAndOperatorsEnum::Eight:
case NumbersAndOperatorsEnum::Nine:
processedDigit = true;
break;
case NumbersAndOperatorsEnum::Add:
case NumbersAndOperatorsEnum::Subtract:
case NumbersAndOperatorsEnum::Multiply:
case NumbersAndOperatorsEnum::Divide:
isPreviousOperator = true;
break;
sendNegate = negateStack.back();
negateStack.pop_back();
canSendNegate = true;
}
if (sendCommand)
else
{
sentEquals = (mappedNumOp == NumbersAndOperatorsEnum::Equals);
Command cmdenum = ConvertToOperatorsEnum(mappedNumOp);
m_standardCalculatorManager.SendCommand(cmdenum);
// Don't send a closing parenthesis if a matching opening parenthesis hasn't been sent already
sendCommand = false;
}
break;
// The CalcEngine state machine won't allow the negate command to be sent before any
// other digits, so instead a flag is set and the command is sent after the first appropriate
// command.
if (sendNegate)
case NumbersAndOperatorsEnum::Zero:
case NumbersAndOperatorsEnum::One:
case NumbersAndOperatorsEnum::Two:
case NumbersAndOperatorsEnum::Three:
case NumbersAndOperatorsEnum::Four:
case NumbersAndOperatorsEnum::Five:
case NumbersAndOperatorsEnum::Six:
case NumbersAndOperatorsEnum::Seven:
case NumbersAndOperatorsEnum::Eight:
case NumbersAndOperatorsEnum::Nine:
processedDigit = true;
break;
case NumbersAndOperatorsEnum::Add:
case NumbersAndOperatorsEnum::Subtract:
case NumbersAndOperatorsEnum::Multiply:
case NumbersAndOperatorsEnum::Divide:
isPreviousOperator = true;
break;
}
if (sendCommand)
{
sentEquals = (mappedNumOp == NumbersAndOperatorsEnum::Equals);
Command cmdenum = ConvertToOperatorsEnum(mappedNumOp);
m_standardCalculatorManager.SendCommand(cmdenum);
// The CalcEngine state machine won't allow the negate command to be sent before any
// other digits, so instead a flag is set and the command is sent after the first appropriate
// command.
if (sendNegate)
{
if (canSendNegate)
{
if (canSendNegate)
{
Command cmdNegate = ConvertToOperatorsEnum(NumbersAndOperatorsEnum::Negate);
m_standardCalculatorManager.SendCommand(cmdNegate);
}
Command cmdNegate = ConvertToOperatorsEnum(NumbersAndOperatorsEnum::Negate);
m_standardCalculatorManager.SendCommand(cmdNegate);
}
// Can't send negate on a leading zero, so wait until the appropriate time to send it.
if (NumbersAndOperatorsEnum::Zero != mappedNumOp && NumbersAndOperatorsEnum::Decimal != mappedNumOp)
{
sendNegate = false;
}
// Can't send negate on a leading zero, so wait until the appropriate time to send it.
if (NumbersAndOperatorsEnum::Zero != mappedNumOp && NumbersAndOperatorsEnum::Decimal != mappedNumOp)
{
sendNegate = false;
}
}
}

View file

@ -18,6 +18,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CalcViewModel", "CalcViewMo
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CalculatorUnitTests", "CalculatorUnitTests\CalculatorUnitTests.vcxproj", "{D3BAED2C-4B07-4E1D-8807-9D6499450349}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CalculatorUITests", "CalculatorUITests\CalculatorUITests.csproj", "{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
@ -102,6 +104,22 @@ Global
{D3BAED2C-4B07-4E1D-8807-9D6499450349}.Release|x86.ActiveCfg = Release|Win32
{D3BAED2C-4B07-4E1D-8807-9D6499450349}.Release|x86.Build.0 = Release|Win32
{D3BAED2C-4B07-4E1D-8807-9D6499450349}.Release|x86.Deploy.0 = Release|Win32
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Debug|ARM.ActiveCfg = Debug|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Debug|ARM.Build.0 = Debug|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Debug|ARM64.Build.0 = Debug|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Debug|x64.ActiveCfg = Debug|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Debug|x64.Build.0 = Debug|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Debug|x86.ActiveCfg = Debug|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Debug|x86.Build.0 = Debug|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Release|ARM.ActiveCfg = Release|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Release|ARM.Build.0 = Release|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Release|ARM64.ActiveCfg = Release|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Release|ARM64.Build.0 = Release|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Release|x64.ActiveCfg = Release|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Release|x64.Build.0 = Release|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Release|x86.ActiveCfg = Release|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -615,8 +615,7 @@
x:Uid="RefreshButtonText"
Foreground="{ThemeResource SystemControlHyperlinkBaseHighBrush}"
Click="CurrencyRefreshButton_Click"/>
<TextBlock Margin="0,7,0,0" Style="{ThemeResource CaptionTextBlockStyle}">
<Run x:Name="Spacing" Text="&#x200A;"/>
<TextBlock Margin="3,7,0,0" Style="{ThemeResource CaptionTextBlockStyle}">
<Run x:Name="CurrencySecondaryStatus"
FontWeight="SemiBold"
Text=""/>

View file

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using System;
namespace CalculatorUITests
{
public class CalculatorSession
{
// Note: append /wd/hub to the URL if you're directing the test at Appium
private const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723";
private const string CalculatorAppId = "Microsoft.WindowsCalculator.Dev_8wekyb3d8bbwe!App";
protected static WindowsDriver<WindowsElement> session;
public static void Setup(TestContext context)
{
// Launch Calculator application if it is not yet launched
if (session == null)
{
// Create a new session to bring up an instance of the Calculator application
// Note: Multiple calculator windows (instances) share the same process Id
var options = new AppiumOptions();
options.AddAdditionalCapability("app", CalculatorAppId);
options.AddAdditionalCapability("deviceName", "WindowsPC");
session = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), options);
session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(180);
Assert.IsNotNull(session);
}
}
public static void TearDown()
{
// Close the application and delete the session
if (session != null)
{
session.Quit();
session = null;
}
}
}
}

View file

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
<PackageReference Include="Appium.WebDriver" Version="4.0.0.6-beta" />
</ItemGroup>
<ItemGroup>
<None Update="CalculatorUITests.runsettings">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector uri="datacollector://microsoft/VideoRecorder/1.0" assemblyQualifiedName="Microsoft.VisualStudio.TestTools.DataCollection.VideoRecorder.VideoRecorderDataCollector, Microsoft.VisualStudio.TestTools.DataCollection.VideoRecorder, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" friendlyName="Screen and Voice Recorder">
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>

View file

@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium.Windows;
using System.Threading;
using System;
namespace CalculatorUITests
{
[TestClass]
public class StandardModeTests : CalculatorSession
{
private static WindowsElement header;
private static WindowsElement calculatorResult;
[TestMethod]
public void Addition()
{
// Find the buttons by their names and click them in sequence to perform 1 + 7 = 8
session.FindElementByName("One").Click();
session.FindElementByName("Plus").Click();
session.FindElementByName("Seven").Click();
session.FindElementByName("Equals").Click();
Assert.AreEqual("8", GetCalculatorResultText());
}
[TestMethod]
public void Division()
{
// Find the buttons by their accessibility ids and click them in sequence to perform 88 / 11 = 8
session.FindElementByAccessibilityId("num8Button").Click();
session.FindElementByAccessibilityId("num8Button").Click();
session.FindElementByAccessibilityId("divideButton").Click();
session.FindElementByAccessibilityId("num1Button").Click();
session.FindElementByAccessibilityId("num1Button").Click();
session.FindElementByAccessibilityId("equalButton").Click();
Assert.AreEqual("8", GetCalculatorResultText());
}
[TestMethod]
public void Multiplication()
{
session.FindElementByAccessibilityId("num9Button").Click();
session.FindElementByAccessibilityId("multiplyButton").Click();
session.FindElementByAccessibilityId("num9Button").Click();
session.FindElementByAccessibilityId("equalButton").Click();
Assert.AreEqual("81", GetCalculatorResultText());
}
[TestMethod]
public void Subtraction()
{
// Find the buttons by their accessibility ids using XPath and click them in sequence to perform 9 - 1 = 8
session.FindElementByAccessibilityId("num9Button").Click();
session.FindElementByAccessibilityId("minusButton").Click();
session.FindElementByAccessibilityId("num1Button").Click();
session.FindElementByAccessibilityId("equalButton").Click();
Assert.AreEqual("8", GetCalculatorResultText());
}
[ClassInitialize]
public static void ClassInitialize(TestContext context)
{
// Create session to launch a Calculator window
Setup(context);
// Identify calculator mode by locating the header
try
{
header = session.FindElementByAccessibilityId("Header");
}
catch
{
header = session.FindElementByAccessibilityId("ContentPresenter");
}
// Ensure that calculator is in standard mode
if (!header.Text.Equals("Standard", StringComparison.OrdinalIgnoreCase))
{
session.FindElementByAccessibilityId("TogglePaneButton").Click();
Thread.Sleep(TimeSpan.FromSeconds(1));
var splitViewPane = session.FindElementByClassName("SplitViewPane");
splitViewPane.FindElementByName("Standard Calculator").Click();
Thread.Sleep(TimeSpan.FromSeconds(1));
Assert.IsTrue(header.Text.Equals("Standard", StringComparison.OrdinalIgnoreCase));
}
// Locate the calculatorResult element
calculatorResult = session.FindElementByAccessibilityId("CalculatorResults");
Assert.IsNotNull(calculatorResult);
}
[ClassCleanup]
public static void ClassCleanup()
{
TearDown();
}
[TestInitialize]
public void Clear()
{
session.FindElementByName("Clear").Click();
Assert.AreEqual("0", GetCalculatorResultText());
}
private string GetCalculatorResultText()
{
return calculatorResult.Text.Replace("Display is", string.Empty).Trim();
}
}
}

File diff suppressed because it is too large Load diff