mirror of
https://github.com/Microsoft/calculator.git
synced 2025-08-22 22:23:29 -07:00
Merge branch 'master' into fix-code-style
This commit is contained in:
commit
e07f53d053
20 changed files with 1128 additions and 1071 deletions
23
.gitattributes
vendored
23
.gitattributes
vendored
|
@ -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
1
.gitignore
vendored
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
52
build/pipelines/templates/run-ui-tests.yaml
Normal file
52
build/pipelines/templates/run-ui-tests.yaml
Normal 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
|
|
@ -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]:####################################################################################################
|
||||
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=" "/>
|
||||
<TextBlock Margin="3,7,0,0" Style="{ThemeResource CaptionTextBlockStyle}">
|
||||
<Run x:Name="CurrencySecondaryStatus"
|
||||
FontWeight="SemiBold"
|
||||
Text=""/>
|
||||
|
|
44
src/CalculatorUITests/CalculatorSession.cs
Normal file
44
src/CalculatorUITests/CalculatorSession.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
src/CalculatorUITests/CalculatorUITests.csproj
Normal file
22
src/CalculatorUITests/CalculatorUITests.csproj
Normal 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>
|
9
src/CalculatorUITests/CalculatorUITests.runsettings
Normal file
9
src/CalculatorUITests/CalculatorUITests.runsettings
Normal 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>
|
112
src/CalculatorUITests/ScenarioStandard.cs
Normal file
112
src/CalculatorUITests/ScenarioStandard.cs
Normal 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
Loading…
Add table
Add a link
Reference in a new issue