mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-01 16:09:46 +02:00
Compare commits
16 Commits
copilot/fi
...
copilot/im
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab08e318d8 | ||
|
|
ce0439f580 | ||
|
|
5b7d3c0a82 | ||
|
|
5e12d2476d | ||
|
|
3b6bf9c0ce | ||
|
|
9020b18a9f | ||
|
|
f59f67cb08 | ||
|
|
890ea40f8a | ||
|
|
70dd9db67a | ||
|
|
fb7c945a2c | ||
|
|
c72580c8f2 | ||
|
|
ce6debf68b | ||
|
|
1eca1713e1 | ||
|
|
1254cba088 | ||
|
|
b5373cbb2b | ||
|
|
b82e6c508d |
7
.github/actions/spell-check/allow/code.txt
vendored
7
.github/actions/spell-check/allow/code.txt
vendored
@@ -328,6 +328,9 @@ MRUCMPPROC
|
||||
MRUINFO
|
||||
REGSTR
|
||||
|
||||
# Quick Accent
|
||||
Ene
|
||||
|
||||
# Misc Win32 APIs and PInvokes
|
||||
INVOKEIDLIST
|
||||
MEMORYSTATUSEX
|
||||
@@ -389,5 +392,9 @@ nostdin
|
||||
engtype
|
||||
Nonpaged
|
||||
|
||||
# Spell-check fragments
|
||||
traies
|
||||
udit
|
||||
|
||||
# XAML
|
||||
Untargeted
|
||||
|
||||
5
.github/actions/spell-check/expect.txt
vendored
5
.github/actions/spell-check/expect.txt
vendored
@@ -791,7 +791,6 @@ lowlevel
|
||||
LOWORD
|
||||
lparam
|
||||
LPBITMAPINFOHEADER
|
||||
LPCFHOOKPROC
|
||||
lpch
|
||||
LPCITEMIDLIST
|
||||
LPCLSID
|
||||
@@ -833,7 +832,6 @@ lstrlen
|
||||
LTEXT
|
||||
LTRREADING
|
||||
luid
|
||||
LUMA
|
||||
lusrmgr
|
||||
LVal
|
||||
LWA
|
||||
@@ -852,7 +850,6 @@ MAPPEDTOSAMEKEY
|
||||
MAPTOSAMESHORTCUT
|
||||
MAPVK
|
||||
MARKDOWNPREVIEWHANDLERCPP
|
||||
MAXDWORD
|
||||
MAXSHORTCUTSIZE
|
||||
maxversiontested
|
||||
MBM
|
||||
@@ -917,7 +914,6 @@ MOUSEINPUT
|
||||
MOVESIZEEND
|
||||
MOVESIZESTART
|
||||
MRM
|
||||
MRT
|
||||
mru
|
||||
msc
|
||||
mscorlib
|
||||
@@ -2303,7 +2299,6 @@ virama
|
||||
vnd
|
||||
vredraw
|
||||
VSpeed
|
||||
VSync
|
||||
WASDK
|
||||
WCRAPI
|
||||
wft
|
||||
|
||||
@@ -85,6 +85,14 @@
|
||||
<ForceImportBeforeCppProps>$(RepoRoot)Cpp.Build.props</ForceImportBeforeCppProps>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Force all C# projects to reference these packages from NuGet so their 10.x versions
|
||||
take precedence over the 9.x versions bundled in the .NET 9 runtime. Without this,
|
||||
projects that don't transitively depend on these packages would get the runtime 9.x
|
||||
version, causing deps.json version conflicts with projects that do. -->
|
||||
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
|
||||
<PackageReference Include="System.Diagnostics.EventLog" />
|
||||
<PackageReference Include="System.Threading.Channels" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj' and '$(_IsSkippedTestProject)' != 'true'">
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
|
||||
@@ -42,26 +42,26 @@
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
|
||||
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.250303.1" />
|
||||
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.9.1-preview.1.25474.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.4.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.4.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.AI.Foundry.Local" Version="0.3.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.66.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.OpenAI" Version="1.66.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.AzureAIInference" Version="1.66.0-beta" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Google" Version="1.66.0-alpha" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.MistralAI" Version="1.66.0-alpha" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Ollama" Version="1.66.0-alpha" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.74.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.OpenAI" Version="1.74.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.AzureAIInference" Version="1.74.0-beta" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Google" Version="1.74.0-alpha" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.MistralAI" Version="1.74.0-alpha" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Ollama" Version="1.74.0-alpha" />
|
||||
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
|
||||
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
|
||||
@@ -89,11 +89,11 @@
|
||||
<PackageVersion Include="MSTest" Version="$(MSTestVersion)" />
|
||||
<PackageVersion Include="MSTest.TestFramework" Version="$(MSTestVersion)" />
|
||||
<PackageVersion Include="NJsonSchema" Version="11.4.0" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageVersion Include="NLog" Version="5.2.8" />
|
||||
<PackageVersion Include="NLog.Extensions.Logging" Version="5.3.8" />
|
||||
<PackageVersion Include="NLog.Schema" Version="5.2.8" />
|
||||
<PackageVersion Include="OpenAI" Version="2.5.0" />
|
||||
<PackageVersion Include="OpenAI" Version="2.9.1" />
|
||||
<PackageVersion Include="Polly.Core" Version="8.6.5" />
|
||||
<PackageVersion Include="ReverseMarkdown" Version="4.1.0" />
|
||||
<PackageVersion Include="RtfPipe" Version="2.0.7677.4303" />
|
||||
@@ -113,23 +113,27 @@
|
||||
<PackageVersion Include="System.Data.OleDb" Version="9.0.10" />
|
||||
<!-- Package System.Data.SqlClient added to force it as a dependency of Microsoft.Windows.Compatibility to the latest version available at this time. -->
|
||||
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
|
||||
<!-- Package System.Diagnostics.EventLog added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.10" />
|
||||
<!-- Package System.Diagnostics.EventLog force-pinned to 10.x to match other Microsoft.Extensions packages;
|
||||
referenced by all C# projects (via Directory.Build.props) to override the 9.x version from the .NET runtime. -->
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.5" />
|
||||
<!-- Package System.Diagnostics.PerformanceCounter added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.11. -->
|
||||
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.10" />
|
||||
<PackageVersion Include="System.ClientModel" Version="1.7.0" />
|
||||
<PackageVersion Include="System.ClientModel" Version="1.9.0" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="9.0.10" />
|
||||
<PackageVersion Include="System.IO.Abstractions" Version="22.0.13" />
|
||||
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="System.Numerics.Tensors" Version="9.0.11" />
|
||||
<PackageVersion Include="System.Numerics.Tensors" Version="10.0.4" />
|
||||
<!-- Including System.Threading.Channels to force version across all projects;
|
||||
referenced by all C# projects (via Directory.Build.props) to override the 9.x version from the .NET runtime. -->
|
||||
<PackageVersion Include="System.Threading.Channels" Version="10.0.4" />
|
||||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="9.0.10" />
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.10" />
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.5" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.5" />
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageVersion Include="ToolGood.Words.Pinyin" Version="3.1.0.3" />
|
||||
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
|
||||
|
||||
@@ -791,6 +791,10 @@
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/peek/peek/peek.vcxproj" Id="a1425b53-3d61-4679-8623-e64a0d3d0a48" />
|
||||
<Project Path="src/modules/peek/Peek.Common.UnitTests/Peek.Common.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/PowerAccent/">
|
||||
<Project Path="src/modules/poweraccent/PowerAccent.Core/PowerAccent.Core.csproj">
|
||||
@@ -803,6 +807,10 @@
|
||||
</Project>
|
||||
<Project Path="src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.vcxproj" Id="c97d9a5d-206c-454e-997e-009e227d7f02" />
|
||||
<Project Path="src/modules/poweraccent/PowerAccentModuleInterface/PowerAccentModuleInterface.vcxproj" Id="34a354c5-23c7-4343-916c-c52daf4fc39d" />
|
||||
<Project Path="src/modules/poweraccent/PowerAccent.Core.UnitTests/PowerAccent.Core.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/PowerOCR/">
|
||||
<Project Path="src/modules/PowerOCR/PowerOCR/PowerOCR.csproj">
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
<PackageReference Include="MSTest" />
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" />
|
||||
<PackageReference Include="System.Diagnostics.EventLog">
|
||||
<!-- This package is a dependency of Microsoft.Extensions.Logging.EventLog, but we need to set it here so we can exclude the assets, so it doesn't conflict with the 8.0.1 dll coming from .NET SDK. -->
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
<!-- System.Diagnostics.EventLog is now provided to all C# projects via Directory.Build.props -->
|
||||
<!-- HACK: CmdPal uses CommunityToolkit.Common directly. Align the version. -->
|
||||
<PackageReference Include="CommunityToolkit.Common" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Diagnostics.EventLog">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
<!-- System.Diagnostics.EventLog is now provided to all C# projects via Directory.Build.props -->
|
||||
<PackageReference Include="System.Diagnostics.PerformanceCounter">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
|
||||
@@ -42,9 +42,7 @@
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Diagnostics.EventLog">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
<!-- System.Diagnostics.EventLog is now provided to all C# projects via Directory.Build.props -->
|
||||
<PackageReference Include="System.Diagnostics.PerformanceCounter">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" />
|
||||
<PackageReference Include="System.Diagnostics.EventLog">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
<!-- System.Diagnostics.EventLog is now provided to all C# projects via Directory.Build.props -->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -35,9 +35,7 @@
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Diagnostics.EventLog">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
<!-- System.Diagnostics.EventLog is now provided to all C# projects via Directory.Build.props -->
|
||||
<PackageReference Include="System.Diagnostics.PerformanceCounter">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
|
||||
283
src/modules/peek/Peek.Common.UnitTests/MathHelperTests.cs
Normal file
283
src/modules/peek/Peek.Common.UnitTests/MathHelperTests.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Peek.Common.Helpers;
|
||||
|
||||
namespace Peek.Common.UnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class MathHelperTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int)
|
||||
/// What: Verifies standard modulo for positive numbers (7 mod 3 = 1)
|
||||
/// Why: Baseline correctness — ensures the positive path matches C# % operator
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Modulo_PositiveNumbers_ShouldReturnStandardModulo()
|
||||
{
|
||||
Assert.AreEqual(1, MathHelper.Modulo(7, 3));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int)
|
||||
/// What: Verifies that 0 mod n = 0
|
||||
/// Why: Zero dividend is a common boundary — must not throw or return non-zero
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Modulo_ZeroDividend_ShouldReturnZero()
|
||||
{
|
||||
Assert.AreEqual(0, MathHelper.Modulo(0, 5));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int)
|
||||
/// What: Verifies that exact division yields zero remainder
|
||||
/// Why: Guards against off-by-one in the modulo formula
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Modulo_ExactDivision_ShouldReturnZero()
|
||||
{
|
||||
Assert.AreEqual(0, MathHelper.Modulo(6, 3));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int) — negative dividend path
|
||||
/// What: Verifies that -1 mod 3 = 2 (mathematical modulo, not C# remainder)
|
||||
/// Why: C# % returns -1 for negative dividends; Modulo wraps to positive range
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Modulo_NegativeDividend_ShouldReturnPositiveResult()
|
||||
{
|
||||
Assert.AreEqual(2, MathHelper.Modulo(-1, 3));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int) — negative dividend with larger magnitude
|
||||
/// What: Verifies -7 mod 3 = 2 (wraps correctly for larger negative values)
|
||||
/// Why: Tests the full formula: ((-7 % 3) + 3) % 3 = (-1 + 3) % 3 = 2
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Modulo_NegativeDividend_LargerMagnitude_ShouldWrapCorrectly()
|
||||
{
|
||||
Assert.AreEqual(2, MathHelper.Modulo(-7, 3));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int)
|
||||
/// What: Verifies -6 mod 3 = 0 (exact negative multiple)
|
||||
/// Why: Edge case where negative dividend is an exact multiple of divisor
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Modulo_NegativeDividend_ExactMultiple_ShouldReturnZero()
|
||||
{
|
||||
Assert.AreEqual(0, MathHelper.Modulo(-6, 3));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int)
|
||||
/// What: Verifies n mod 1 = 0 for positive n
|
||||
/// Why: Divisor of 1 always yields 0 — guards against divide-by-zero edge case
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Modulo_PositiveDividend_DivisorOne_ShouldReturnZero()
|
||||
{
|
||||
Assert.AreEqual(0, MathHelper.Modulo(5, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int)
|
||||
/// What: Verifies -n mod 1 = 0 for negative n
|
||||
/// Why: Tests the negative path with divisor=1 (simplest negative case)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Modulo_NegativeDividend_DivisorOne_ShouldReturnZero()
|
||||
{
|
||||
Assert.AreEqual(0, MathHelper.Modulo(-5, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int)
|
||||
/// What: Verifies modulo works with large numbers (1000001 mod 1000000 = 1)
|
||||
/// Why: Tests that no integer overflow occurs in the formula for large operands
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Modulo_LargePositiveNumbers_ShouldWork()
|
||||
{
|
||||
Assert.AreEqual(1, MathHelper.Modulo(1000001, 1000000));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int)
|
||||
/// What: Verifies that dividend less than divisor returns the dividend itself
|
||||
/// Why: 2 mod 5 = 2 — no division happens, just returns the dividend
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Modulo_DividendLessThanDivisor_ShouldReturnDividend()
|
||||
{
|
||||
Assert.AreEqual(2, MathHelper.Modulo(2, 5));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int) — negative dividend
|
||||
/// What: Verifies -2 mod 3 = 1 (mathematical modulo)
|
||||
/// Why: Tests another negative wrap case: ((-2 % 3) + 3) % 3 = (-2 + 3) % 3 = 1
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Modulo_NegativeDividend_MinusTwo_ModThree_ShouldReturnOne()
|
||||
{
|
||||
Assert.AreEqual(1, MathHelper.Modulo(-2, 3));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.Modulo(int, int) — zero divisor
|
||||
/// What: Documents that dividing by zero throws DivideByZeroException
|
||||
/// Why: Edge case — callers must handle zero divisor; the method does not guard against it
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void Modulo_ZeroDivisor_ShouldThrow()
|
||||
{
|
||||
MathHelper.Modulo(5, 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void Modulo_NegativeDivisor_ShouldThrow()
|
||||
{
|
||||
MathHelper.Modulo(-1, -3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.NumberOfDigits(int)
|
||||
/// What: Verifies that 0 has 1 digit
|
||||
/// Why: Zero is a special case — Math.Abs(0).ToString() = "0" which has length 1
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NumberOfDigits_Zero_ShouldReturnOne()
|
||||
{
|
||||
Assert.AreEqual(1, MathHelper.NumberOfDigits(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.NumberOfDigits(int)
|
||||
/// What: Verifies single-digit number returns 1
|
||||
/// Why: Baseline case for the simplest positive input
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NumberOfDigits_SingleDigit_ShouldReturnOne()
|
||||
{
|
||||
Assert.AreEqual(1, MathHelper.NumberOfDigits(5));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.NumberOfDigits(int)
|
||||
/// What: Verifies two-digit number returns 2
|
||||
/// Why: Tests the transition from single to multi-digit
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NumberOfDigits_TwoDigits_ShouldReturnTwo()
|
||||
{
|
||||
Assert.AreEqual(2, MathHelper.NumberOfDigits(42));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.NumberOfDigits(int)
|
||||
/// What: Verifies three-digit number (100) returns 3
|
||||
/// Why: Tests exact power of 10 boundary (100 vs 99)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NumberOfDigits_ThreeDigits_ShouldReturnThree()
|
||||
{
|
||||
Assert.AreEqual(3, MathHelper.NumberOfDigits(100));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.NumberOfDigits(int) — negative number
|
||||
/// What: Verifies that sign is ignored via Math.Abs
|
||||
/// Why: Negative sign is not a digit — only magnitude matters
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NumberOfDigits_NegativeNumber_ShouldIgnoreSign()
|
||||
{
|
||||
Assert.AreEqual(3, MathHelper.NumberOfDigits(-123));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.NumberOfDigits(int) — int.MaxValue
|
||||
/// What: Verifies int.MaxValue (2147483647) returns 10 digits
|
||||
/// Why: Upper boundary of int range — ensures no overflow in Math.Abs path
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NumberOfDigits_MaxValue_ShouldReturnTenDigits()
|
||||
{
|
||||
Assert.AreEqual(10, MathHelper.NumberOfDigits(int.MaxValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.NumberOfDigits(int) — near int.MinValue
|
||||
/// What: Verifies int.MinValue + 1 (-2147483647) returns 10 digits
|
||||
/// Why: int.MinValue itself overflows Math.Abs — this tests the safe boundary
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NumberOfDigits_MinValue_ShouldHandleAbsoluteValue()
|
||||
{
|
||||
Assert.AreEqual(10, MathHelper.NumberOfDigits(int.MinValue + 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.NumberOfDigits(int) — int.MinValue
|
||||
/// What: Documents that int.MinValue causes OverflowException due to Math.Abs
|
||||
/// Why: Edge case — Math.Abs(-2147483648) has no positive int representation
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(OverflowException))]
|
||||
public void NumberOfDigits_MinValue_ShouldThrowOverflow()
|
||||
{
|
||||
MathHelper.NumberOfDigits(int.MinValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.NumberOfDigits(int) — powers of 10
|
||||
/// What: Verifies digit count at each power-of-10 boundary (1, 10, 100, 1000, 10000)
|
||||
/// Why: Powers of 10 are the exact transition points — off-by-one bugs surface here
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NumberOfDigits_PowerOfTen_ShouldReturnCorrectCount()
|
||||
{
|
||||
Assert.AreEqual(1, MathHelper.NumberOfDigits(1));
|
||||
Assert.AreEqual(2, MathHelper.NumberOfDigits(10));
|
||||
Assert.AreEqual(3, MathHelper.NumberOfDigits(100));
|
||||
Assert.AreEqual(4, MathHelper.NumberOfDigits(1000));
|
||||
Assert.AreEqual(5, MathHelper.NumberOfDigits(10000));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.NumberOfDigits(int) — 9→10 boundary
|
||||
/// What: Verifies that 9 returns 1 digit and 10 returns 2 digits
|
||||
/// Why: Tests the first single-to-double digit transition
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NumberOfDigits_BoundaryValues_NineAndTen()
|
||||
{
|
||||
Assert.AreEqual(1, MathHelper.NumberOfDigits(9));
|
||||
Assert.AreEqual(2, MathHelper.NumberOfDigits(10));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: MathHelper.NumberOfDigits(int) — 99→100 boundary
|
||||
/// What: Verifies that 99 returns 2 digits and 100 returns 3 digits
|
||||
/// Why: Tests the double-to-triple digit transition
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NumberOfDigits_BoundaryValues_NinetyNineAndHundred()
|
||||
{
|
||||
Assert.AreEqual(2, MathHelper.NumberOfDigits(99));
|
||||
Assert.AreEqual(3, MathHelper.NumberOfDigits(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
156
src/modules/peek/Peek.Common.UnitTests/PathHelperTests.cs
Normal file
156
src/modules/peek/Peek.Common.UnitTests/PathHelperTests.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Peek.Common.Helpers;
|
||||
|
||||
namespace Peek.Common.UnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class PathHelperTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies a standard UNC path (\\server\share) is recognized
|
||||
/// Why: Baseline positive case — the most common UNC format
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_StandardUncPath_ShouldReturnTrue()
|
||||
{
|
||||
Assert.IsTrue(PathHelper.IsUncPath(@"\\server\share"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies a UNC path with nested subfolders and file is recognized
|
||||
/// Why: Real-world UNC paths include subfolders — must not fail on deeper paths
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_UncPathWithSubfolder_ShouldReturnTrue()
|
||||
{
|
||||
Assert.IsTrue(PathHelper.IsUncPath(@"\\server\share\folder\file.txt"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies a UNC path with dotted server name (FQDN) is recognized
|
||||
/// Why: Enterprise environments use FQDN server names (e.g., server.corp.com)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_UncPathWithDottedServer_ShouldReturnTrue()
|
||||
{
|
||||
Assert.IsTrue(PathHelper.IsUncPath(@"\\server.domain.com\share"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies a UNC path with IP address is recognized
|
||||
/// Why: Some network shares use IP addresses instead of hostnames
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_UncPathWithIPAddress_ShouldReturnTrue()
|
||||
{
|
||||
Assert.IsTrue(PathHelper.IsUncPath(@"\\192.168.1.1\share"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies a local drive path (C:\...) is not classified as UNC
|
||||
/// Why: Drive-letter paths are local — confusing them with UNC would break file access
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_LocalDrivePath_ShouldReturnFalse()
|
||||
{
|
||||
Assert.IsFalse(PathHelper.IsUncPath(@"C:\Users\test\file.txt"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies a bare drive root (D:\) is not classified as UNC
|
||||
/// Why: Drive roots are local paths — shortest possible local absolute path
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_LocalRootPath_ShouldReturnFalse()
|
||||
{
|
||||
Assert.IsFalse(PathHelper.IsUncPath(@"D:\"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies a relative path is not classified as UNC
|
||||
/// Why: Relative paths have no server component — Uri.TryCreate fails for them
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_RelativePath_ShouldReturnFalse()
|
||||
{
|
||||
Assert.IsFalse(PathHelper.IsUncPath(@"folder\file.txt"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies an empty string returns false without throwing
|
||||
/// Why: Empty input is a common edge case — must not crash
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_EmptyString_ShouldReturnFalse()
|
||||
{
|
||||
Assert.IsFalse(PathHelper.IsUncPath(string.Empty));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies an HTTP URL is not classified as UNC
|
||||
/// Why: HTTP URLs are not file paths — Uri.IsUnc must return false for them
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_HttpUrl_ShouldReturnFalse()
|
||||
{
|
||||
Assert.IsFalse(PathHelper.IsUncPath("http://example.com/path"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies a file:/// URI (local file) is not classified as UNC
|
||||
/// Why: file:///C:/... is a local file URI, not a network UNC path
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_FileUri_ShouldReturnFalse()
|
||||
{
|
||||
Assert.IsFalse(PathHelper.IsUncPath("file:///C:/Users/test/file.txt"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies a standard UNC path with a file component (\\server\share\file.txt)
|
||||
/// Why: Tests UNC with a trailing file name — distinct from share-only paths
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_StandardUncWithFile_ShouldReturnTrue()
|
||||
{
|
||||
Assert.IsTrue(PathHelper.IsUncPath(@"\\server\share\file.txt"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies a single-backslash prefix is not classified as UNC
|
||||
/// Why: UNC requires exactly two leading backslashes — one backslash is not UNC
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_SingleBackslash_ShouldReturnFalse()
|
||||
{
|
||||
Assert.IsFalse(PathHelper.IsUncPath(@"\server\share"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: PathHelper.IsUncPath(string)
|
||||
/// What: Verifies null input returns false without throwing
|
||||
/// Why: Null is a common edge case — Uri.TryCreate handles null gracefully
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsUncPath_NullInput_ShouldReturnFalse()
|
||||
{
|
||||
Assert.IsFalse(PathHelper.IsUncPath(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\Peek.Common.Tests\</OutputPath>
|
||||
<RootNamespace>Peek.Common.UnitTests</RootNamespace>
|
||||
<AssemblyName>PowerToys.Peek.Common.UnitTests</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<!-- Pull in the WindowsDesktop shared framework so that runtime DLLs (System.CodeDom,
|
||||
Microsoft.VisualBasic, WindowsBase, etc.) resolve to the same version as all other
|
||||
projects in the solution. Without this, transitive NuGet packages provide older
|
||||
versions that fail the verifyDepsJsonLibraryVersions CI check. -->
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Peek.Common\Peek.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -11,6 +11,11 @@ namespace Peek.Common.Helpers
|
||||
{
|
||||
public static int Modulo(int a, int b)
|
||||
{
|
||||
if (b <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(b), b, "Divisor must be positive.");
|
||||
}
|
||||
|
||||
return a < 0 ? ((a % b) + b) % b : a % b;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PowerAccent.Core.Services;
|
||||
using PowerAccent.Core.Tools;
|
||||
|
||||
namespace PowerAccent.Core.UnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class CalculationTests
|
||||
{
|
||||
// Screen representing a standard 1920x1080 monitor at position (0,0)
|
||||
private static readonly Rect StandardScreen = new Rect(0, 0, 1920, 1080);
|
||||
|
||||
// A typical toolbar window size (300x50 in WPF DIPs)
|
||||
private static readonly Size ToolbarWindow = new Size(300, 50);
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromCaret
|
||||
/// What: Verifies toolbar is centered horizontally above the caret at screen center
|
||||
/// Why: The most common placement scenario — regression here affects every user
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromCaret_CenterOfScreen_ShouldCenterToolbar()
|
||||
{
|
||||
var caret = new Point(960, 540);
|
||||
var result = Calculation.GetRawCoordinatesFromCaret(caret, StandardScreen, ToolbarWindow);
|
||||
|
||||
// X should be caret.X - window.Width/2 = 960 - 150 = 810
|
||||
Assert.AreEqual(810.0, result.X);
|
||||
|
||||
// Y should be caret.Y - window.Height - 20 = 540 - 50 - 20 = 470
|
||||
Assert.AreEqual(470.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromCaret — left edge clamping
|
||||
/// What: Verifies toolbar is clamped to screen left when caret is near left edge
|
||||
/// Why: Without clamping, toolbar would extend off-screen and be unusable
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromCaret_NearLeftEdge_ShouldClampToScreenLeft()
|
||||
{
|
||||
var caret = new Point(50, 540);
|
||||
var result = Calculation.GetRawCoordinatesFromCaret(caret, StandardScreen, ToolbarWindow);
|
||||
|
||||
// left = 50 - 150 = -100, which is < screen.X (0), so X should be clamped to 0
|
||||
Assert.AreEqual(0.0, result.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromCaret — right edge clamping
|
||||
/// What: Verifies toolbar is clamped to screen right when caret is near right edge
|
||||
/// Why: Toolbar extending past screen right would be clipped by the display
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromCaret_NearRightEdge_ShouldClampToScreenRight()
|
||||
{
|
||||
var caret = new Point(1900, 540);
|
||||
var result = Calculation.GetRawCoordinatesFromCaret(caret, StandardScreen, ToolbarWindow);
|
||||
|
||||
// left = 1900 - 150 = 1750, left + window.Width = 1750 + 300 = 2050 > 1920
|
||||
// So X should be clamped to screen.X + screen.Width - window.Width = 1920 - 300 = 1620
|
||||
Assert.AreEqual(1620.0, result.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromCaret — top edge flip
|
||||
/// What: Verifies toolbar is placed below the caret when there is no room above
|
||||
/// Why: When caret is near top of screen, toolbar must flip below to remain visible
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromCaret_NearTopEdge_ShouldPlaceBelow()
|
||||
{
|
||||
var caret = new Point(960, 30);
|
||||
var result = Calculation.GetRawCoordinatesFromCaret(caret, StandardScreen, ToolbarWindow);
|
||||
|
||||
// top = 30 - 50 - 20 = -40, which is < screen.Y (0)
|
||||
// So Y should be caret.Y + 20 = 50 (placed below caret)
|
||||
Assert.AreEqual(50.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromCaret — normal above placement
|
||||
/// What: Verifies toolbar is placed above the caret when there is sufficient space
|
||||
/// Why: Default behavior — toolbar should appear above to avoid occluding text below
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromCaret_SufficientSpaceAbove_ShouldPlaceAbove()
|
||||
{
|
||||
var caret = new Point(960, 200);
|
||||
var result = Calculation.GetRawCoordinatesFromCaret(caret, StandardScreen, ToolbarWindow);
|
||||
|
||||
// top = 200 - 50 - 20 = 130, which is >= screen.Y (0)
|
||||
Assert.AreEqual(130.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromCaret — multi-monitor offset
|
||||
/// What: Verifies toolbar respects non-zero screen origin (second monitor at X=1920)
|
||||
/// Why: Multi-monitor setups have offset screen coordinates — clamping must use screen.X
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromCaret_OffsetScreen_ShouldRespectScreenOrigin()
|
||||
{
|
||||
var screen = new Rect(1920, 0, 1920, 1080);
|
||||
var caret = new Point(1950, 540);
|
||||
var result = Calculation.GetRawCoordinatesFromCaret(caret, screen, ToolbarWindow);
|
||||
|
||||
// left = 1950 - 150 = 1800, which is < screen.X (1920)
|
||||
// So X should be clamped to 1920
|
||||
Assert.AreEqual(1920.0, result.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromCaret — exact boundary case
|
||||
/// What: Verifies no clamping occurs when toolbar fits exactly at the boundary
|
||||
/// Why: Off-by-one errors in boundary checks are common — this catches ≤ vs < bugs
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromCaret_ExactBoundary_ShouldNotClamp()
|
||||
{
|
||||
var caret = new Point(150, 100);
|
||||
var result = Calculation.GetRawCoordinatesFromCaret(caret, StandardScreen, ToolbarWindow);
|
||||
|
||||
// left = 150 - 150 = 0, which == screen.X, so no left clamp needed
|
||||
Assert.AreEqual(0.0, result.X);
|
||||
|
||||
// top = 100 - 50 - 20 = 30, which >= 0
|
||||
Assert.AreEqual(30.0, result.Y);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotImplementedException))]
|
||||
public void GetRawCoordinatesFromPosition_InvalidPosition_ShouldThrow()
|
||||
{
|
||||
Calculation.GetRawCoordinatesFromPosition((Position)999, StandardScreen, ToolbarWindow, 1.0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with Position.Top
|
||||
/// What: Verifies toolbar is horizontally centered at the top of the screen with offset
|
||||
/// Why: Exact value test — catches formula regressions in the Top positioning branch
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_TopCenter_ShouldBeCenteredAtTop()
|
||||
{
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.Top, StandardScreen, ToolbarWindow, 1.0);
|
||||
|
||||
// X: screen.X + screen.Width/2 - window.Width*dpi/2 = 0 + 960 - 150 = 810
|
||||
Assert.AreEqual(810.0, result.X);
|
||||
|
||||
// Y: screen.Y + offset = 0 + 24 = 24
|
||||
Assert.AreEqual(24.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with Position.Bottom
|
||||
/// What: Verifies toolbar is horizontally centered at the bottom of the screen
|
||||
/// Why: Bottom placement subtracts window height + offset — easy to get wrong
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_BottomCenter_ShouldBeCenteredAtBottom()
|
||||
{
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.Bottom, StandardScreen, ToolbarWindow, 1.0);
|
||||
|
||||
// X: centered = 810
|
||||
Assert.AreEqual(810.0, result.X);
|
||||
|
||||
// Y: screen.Y + screen.Height - (window.Height*dpi + offset) = 0 + 1080 - (50 + 24) = 1006
|
||||
Assert.AreEqual(1006.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with Position.Center
|
||||
/// What: Verifies toolbar is centered both horizontally and vertically
|
||||
/// Why: Center is the simplest case — validates the baseline formula for both axes
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_Center_ShouldBeTrulyCentered()
|
||||
{
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.Center, StandardScreen, ToolbarWindow, 1.0);
|
||||
|
||||
// X: screen.X + screen.Width/2 - window.Width*dpi/2 = 0 + 960 - 150 = 810
|
||||
Assert.AreEqual(810.0, result.X);
|
||||
|
||||
// Y: screen.Y + screen.Height/2 - window.Height*dpi/2 = 0 + 540 - 25 = 515
|
||||
Assert.AreEqual(515.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with Position.TopLeft
|
||||
/// What: Verifies toolbar is placed at top-left corner with offset margin
|
||||
/// Why: Corner placements use raw offset — validates the simplest X/Y path
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_TopLeft_ShouldBeAtTopLeftCorner()
|
||||
{
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.TopLeft, StandardScreen, ToolbarWindow, 1.0);
|
||||
|
||||
// X: screen.X + offset = 0 + 24 = 24
|
||||
Assert.AreEqual(24.0, result.X);
|
||||
|
||||
// Y: screen.Y + offset = 0 + 24 = 24
|
||||
Assert.AreEqual(24.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with Position.TopRight
|
||||
/// What: Verifies exact coordinates for top-right corner placement
|
||||
/// Why: TopRight combines right-aligned X with top Y — validates both formula branches
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_TopRight_ShouldBeAtTopRightCorner()
|
||||
{
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.TopRight, StandardScreen, ToolbarWindow, 1.0);
|
||||
|
||||
// X: screen.X + screen.Width - (window.Width*dpi + offset) = 0 + 1920 - (300 + 24) = 1596
|
||||
Assert.AreEqual(1596.0, result.X);
|
||||
|
||||
// Y: screen.Y + offset = 0 + 24 = 24
|
||||
Assert.AreEqual(24.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with Position.BottomLeft
|
||||
/// What: Verifies exact coordinates for bottom-left corner placement
|
||||
/// Why: BottomLeft combines left-aligned X with bottom Y — validates both formula branches
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_BottomLeft_ShouldBeAtBottomLeftCorner()
|
||||
{
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.BottomLeft, StandardScreen, ToolbarWindow, 1.0);
|
||||
|
||||
// X: screen.X + offset = 0 + 24 = 24
|
||||
Assert.AreEqual(24.0, result.X);
|
||||
|
||||
// Y: screen.Y + screen.Height - (window.Height*dpi + offset) = 0 + 1080 - (50 + 24) = 1006
|
||||
Assert.AreEqual(1006.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with Position.BottomRight
|
||||
/// What: Verifies toolbar is placed at bottom-right corner with offset margin
|
||||
/// Why: BottomRight uses the most complex formula (subtracts from both edges)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_BottomRight_ShouldBeAtBottomRightCorner()
|
||||
{
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.BottomRight, StandardScreen, ToolbarWindow, 1.0);
|
||||
|
||||
// X: screen.X + screen.Width - (window.Width*dpi + offset) = 0 + 1920 - (300 + 24) = 1596
|
||||
Assert.AreEqual(1596.0, result.X);
|
||||
|
||||
// Y: screen.Y + screen.Height - (window.Height*dpi + offset) = 0 + 1080 - (50 + 24) = 1006
|
||||
Assert.AreEqual(1006.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with Position.Left
|
||||
/// What: Verifies toolbar is placed at left edge, vertically centered
|
||||
/// Why: Left position uses offset for X and centering for Y — tests the mix
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_Left_ShouldBeAtLeftMiddle()
|
||||
{
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.Left, StandardScreen, ToolbarWindow, 1.0);
|
||||
|
||||
// X: screen.X + offset = 0 + 24 = 24
|
||||
Assert.AreEqual(24.0, result.X);
|
||||
|
||||
// Y: centered vertically = 0 + 540 - 25 = 515
|
||||
Assert.AreEqual(515.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with Position.Right
|
||||
/// What: Verifies toolbar is placed at right edge, vertically centered
|
||||
/// Why: Right position subtracts window width from screen edge — tests the right-align formula
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_Right_ShouldBeAtRightMiddle()
|
||||
{
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.Right, StandardScreen, ToolbarWindow, 1.0);
|
||||
|
||||
// X: screen.X + screen.Width - (window.Width*dpi + offset) = 1920 - 324 = 1596
|
||||
Assert.AreEqual(1596.0, result.X);
|
||||
|
||||
// Y: centered vertically = 515
|
||||
Assert.AreEqual(515.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with DPI=1.5
|
||||
/// What: Verifies that DPI scaling is applied to window size in centering formula
|
||||
/// Why: High-DPI monitors are common — incorrect scaling makes toolbar appear off-center
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_WithHighDpi_ShouldScaleWindow()
|
||||
{
|
||||
double dpi = 1.5;
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.Center, StandardScreen, ToolbarWindow, dpi);
|
||||
|
||||
// X: screen.X + screen.Width/2 - window.Width*dpi/2 = 0 + 960 - (300*1.5)/2 = 960 - 225 = 735
|
||||
Assert.AreEqual(735.0, result.X);
|
||||
|
||||
// Y: screen.Y + screen.Height/2 - window.Height*dpi/2 = 0 + 540 - (50*1.5)/2 = 540 - 37.5 = 502.5
|
||||
Assert.AreEqual(502.5, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with DPI=2.0, TopLeft
|
||||
/// What: Verifies that offset is NOT DPI-scaled for corner positions
|
||||
/// Why: The 24px offset is a fixed margin — DPI scaling only affects window size
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_WithDpi2_TopLeft_ShouldUseOffset()
|
||||
{
|
||||
double dpi = 2.0;
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.TopLeft, StandardScreen, ToolbarWindow, dpi);
|
||||
|
||||
// X: screen.X + offset = 0 + 24 = 24 (offset is not DPI-scaled)
|
||||
Assert.AreEqual(24.0, result.X);
|
||||
|
||||
// Y: screen.Y + offset = 0 + 24 = 24
|
||||
Assert.AreEqual(24.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with DPI=2.0, BottomRight
|
||||
/// What: Verifies DPI scaling of window size in bottom-right corner formula
|
||||
/// Why: At 2x DPI, the window occupies 600×100 pixels — offset from edge must account for this
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_WithDpi2_BottomRight_ShouldScaleWindowSize()
|
||||
{
|
||||
double dpi = 2.0;
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.BottomRight, StandardScreen, ToolbarWindow, dpi);
|
||||
|
||||
// X: screen.X + screen.Width - (window.Width*dpi + offset) = 0 + 1920 - (300*2 + 24) = 1920 - 624 = 1296
|
||||
Assert.AreEqual(1296.0, result.X);
|
||||
|
||||
// Y: screen.Y + screen.Height - (window.Height*dpi + offset) = 0 + 1080 - (50*2 + 24) = 1080 - 124 = 956
|
||||
Assert.AreEqual(956.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Calculation.GetRawCoordinatesFromPosition with offset screen origin
|
||||
/// What: Verifies screen.X is added as the base for TopLeft on a secondary monitor
|
||||
/// Why: Without adding screen origin, toolbar appears on the wrong monitor
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetRawCoordinatesFromPosition_OffsetScreen_ShouldRespectScreenOrigin()
|
||||
{
|
||||
var screen = new Rect(1920, 0, 1920, 1080);
|
||||
var result = Calculation.GetRawCoordinatesFromPosition(Position.TopLeft, screen, ToolbarWindow, 1.0);
|
||||
|
||||
// X: screen.X + offset = 1920 + 24 = 1944
|
||||
Assert.AreEqual(1944.0, result.X);
|
||||
|
||||
// Y: screen.Y + offset = 0 + 24 = 24
|
||||
Assert.AreEqual(24.0, result.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PowerToys.PowerAccentKeyboardService;
|
||||
|
||||
namespace PowerAccent.Core.UnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class LanguagesTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey
|
||||
/// What: Verifies that passing an empty language array returns an empty result
|
||||
/// Why: Guards against NullReferenceException or incorrect fallback when no languages are configured
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_EmptyLanguageArray_ShouldReturnEmpty()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_A, Array.Empty<Language>());
|
||||
Assert.AreEqual(0, result.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.FR
|
||||
/// What: Verifies that French returns at least one accented character for the letter A
|
||||
/// Why: Regression guard — ensures the French language mapping is wired up and non-empty
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_SingleLanguage_ShouldReturnNonEmpty()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_A, new[] { Language.FR });
|
||||
Assert.IsTrue(result.Length > 0, "French should have accented characters for A");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with multiple languages
|
||||
/// What: Verifies that combining French and Spanish merges characters and removes duplicates
|
||||
/// Why: Guards against duplicate entries when languages share accent characters (e.g., à)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_MultipleLanguages_ShouldMergeAndDeduplicate()
|
||||
{
|
||||
var frResult = Languages.GetDefaultLetterKey(LetterKey.VK_A, new[] { Language.FR });
|
||||
var spResult = Languages.GetDefaultLetterKey(LetterKey.VK_A, new[] { Language.SP });
|
||||
var combinedResult = Languages.GetDefaultLetterKey(LetterKey.VK_A, new[] { Language.FR, Language.SP });
|
||||
|
||||
Assert.AreEqual(combinedResult.Length, combinedResult.Distinct().Count(), "Combined result should contain no duplicates");
|
||||
|
||||
foreach (var ch in frResult)
|
||||
{
|
||||
CollectionAssert.Contains(combinedResult, ch, $"Combined result should contain French character '{ch}'");
|
||||
}
|
||||
|
||||
foreach (var ch in spResult)
|
||||
{
|
||||
CollectionAssert.Contains(combinedResult, ch, $"Combined result should contain Spanish character '{ch}'");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.FR for VK_A
|
||||
/// What: Verifies that French returns specific expected accent characters (à, â, æ)
|
||||
/// Why: Pinpoints exact character expectations — catches silent data regressions in language tables
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_French_A_ShouldContainExpectedAccents()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_A, new[] { Language.FR });
|
||||
|
||||
CollectionAssert.Contains(result, "à", "French should contain à");
|
||||
CollectionAssert.Contains(result, "â", "French should contain â");
|
||||
CollectionAssert.Contains(result, "æ", "French should contain æ");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.DE for VK_O
|
||||
/// What: Verifies German returns the ö umlaut for the O key
|
||||
/// Why: Core German character — regression would break German users' workflow
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_German_O_ShouldContainUmlaut()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_O, new[] { Language.DE });
|
||||
CollectionAssert.Contains(result, "ö", "German should contain ö for O");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.DE for VK_U
|
||||
/// What: Verifies German returns the ü umlaut for the U key
|
||||
/// Why: Core German character — regression would break German users' workflow
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_German_U_ShouldContainUmlaut()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_U, new[] { Language.DE });
|
||||
CollectionAssert.Contains(result, "ü", "German should contain ü for U");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.DE for VK_S
|
||||
/// What: Verifies German returns the ß (Eszett) for the S key
|
||||
/// Why: ß is unique to German — its absence would be a critical language regression
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_German_S_ShouldContainEszett()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_S, new[] { Language.DE });
|
||||
CollectionAssert.Contains(result, "ß", "German should contain ß for S");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.SP for VK_N
|
||||
/// What: Verifies Spanish returns the ñ character for the N key
|
||||
/// Why: ñ is the most recognizable Spanish-specific character
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_Spanish_N_ShouldContainEne()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_N, new[] { Language.SP });
|
||||
CollectionAssert.Contains(result, "ñ", "Spanish should contain ñ for N");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.CZ for VK_C
|
||||
/// What: Verifies Czech returns the č (c-caron) for the C key
|
||||
/// Why: č is essential for Czech — its absence would break Czech language support
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_Czech_C_ShouldContainCaron()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_C, new[] { Language.CZ });
|
||||
CollectionAssert.Contains(result, "č", "Czech should contain č for C");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.PL for VK_L
|
||||
/// What: Verifies Polish returns the ł (L with stroke) for the L key
|
||||
/// Why: ł is fundamental to Polish — its absence would break Polish language support
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_Polish_L_ShouldContainStroke()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_L, new[] { Language.PL });
|
||||
CollectionAssert.Contains(result, "ł", "Polish should contain ł for L");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.VI for VK_A
|
||||
/// What: Verifies Vietnamese returns a rich set of A variants (≥10 accented forms)
|
||||
/// Why: Vietnamese has extensive diacritics — guards against truncation of the character table
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_Vietnamese_A_ShouldContainMultipleAccents()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_A, new[] { Language.VI });
|
||||
|
||||
Assert.IsTrue(result.Length >= 10, "Vietnamese should have many accented A characters");
|
||||
CollectionAssert.Contains(result, "à", "Vietnamese should contain à");
|
||||
CollectionAssert.Contains(result, "ă", "Vietnamese should contain ă");
|
||||
CollectionAssert.Contains(result, "â", "Vietnamese should contain â");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.SPECIAL for VK_0
|
||||
/// What: Verifies that digit 0 returns superscript (⁰) and subscript (₀) variants
|
||||
/// Why: Special characters for digits are used in mathematical/scientific input
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_Special_Digits_ShouldReturnSubscriptsSuperscripts()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_0, new[] { Language.SPECIAL });
|
||||
Assert.IsTrue(result.Length > 0, "Special characters for 0 should exist");
|
||||
CollectionAssert.Contains(result, "⁰", "Special 0 should contain superscript 0");
|
||||
CollectionAssert.Contains(result, "₀", "Special 0 should contain subscript 0");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.SPECIAL for VK_1
|
||||
/// What: Verifies that digit 1 returns fraction (½) and superscript (¹) variants
|
||||
/// Why: Fraction characters are commonly needed for recipes, measurements, etc.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_Special_1_ShouldContainFractions()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_1, new[] { Language.SPECIAL });
|
||||
CollectionAssert.Contains(result, "½", "Special 1 should contain ½");
|
||||
CollectionAssert.Contains(result, "¹", "Special 1 should contain ¹");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.CUR
|
||||
/// What: Verifies that Currency language returns well-known currency symbols (€, $, £)
|
||||
/// Why: Currency symbols are the primary use case — tests exact symbol presence, not just non-null
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_Currency_ShouldReturnCurrencySymbols()
|
||||
{
|
||||
var euroResult = Languages.GetDefaultLetterKey(LetterKey.VK_E, new[] { Language.CUR });
|
||||
CollectionAssert.Contains(euroResult, "€", "Currency VK_E should contain Euro symbol");
|
||||
|
||||
var dollarResult = Languages.GetDefaultLetterKey(LetterKey.VK_S, new[] { Language.CUR });
|
||||
CollectionAssert.Contains(dollarResult, "$", "Currency VK_S should contain Dollar symbol");
|
||||
|
||||
var poundResult = Languages.GetDefaultLetterKey(LetterKey.VK_P, new[] { Language.CUR });
|
||||
CollectionAssert.Contains(poundResult, "£", "Currency VK_P should contain Pound symbol");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey with Language.DE for VK_Q
|
||||
/// What: Verifies that a key with no accented characters for a language returns empty
|
||||
/// Why: Guards against spurious characters appearing for keys that should have no accents
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_UnusedKeyForLanguage_ShouldReturnEmpty()
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(LetterKey.VK_Q, new[] { Language.DE });
|
||||
Assert.AreEqual(0, result.Length, "German should have no accented characters for Q");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKeyALL (cache path via ConcurrentDictionary)
|
||||
/// What: Verifies that calling with all languages twice returns the same cached array instance
|
||||
/// Why: Cache hit should return the identical object reference, not a recomputed copy
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_AllLanguages_ShouldReturnCachedResults()
|
||||
{
|
||||
var allLanguages = Enum.GetValues<Language>();
|
||||
|
||||
var result1 = Languages.GetDefaultLetterKey(LetterKey.VK_A, allLanguages);
|
||||
var result2 = Languages.GetDefaultLetterKey(LetterKey.VK_A, allLanguages);
|
||||
|
||||
Assert.IsTrue(result1.Length > 0, "All languages combined should have accented A characters");
|
||||
CollectionAssert.AreEqual(result1, result2, "Cached results should be identical");
|
||||
|
||||
// Verify cache hit returns same object instance, not just equal contents
|
||||
Assert.AreSame(result1, result2, "Second call should return cached reference");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Languages.GetDefaultLetterKey across all LetterKey × Language combinations
|
||||
/// What: Smoke-tests every letter key with all languages to ensure no exceptions are thrown
|
||||
/// Why: Catches missing switch arms or null returns that would crash the toolbar at runtime
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetDefaultLetterKey_AllLanguages_EachLetterKey_ShouldNotThrow()
|
||||
{
|
||||
var allLanguages = Enum.GetValues<Language>();
|
||||
var allLetterKeys = Enum.GetValues<LetterKey>();
|
||||
|
||||
foreach (var letterKey in allLetterKeys)
|
||||
{
|
||||
var result = Languages.GetDefaultLetterKey(letterKey, allLanguages);
|
||||
Assert.IsNotNull(result, $"Result for {letterKey} should not be null");
|
||||
|
||||
Assert.AreEqual(result.Length, result.Distinct().Count(), $"Result for {letterKey} should have no duplicates");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/modules/poweraccent/PowerAccent.Core.UnitTests/PointTests.cs
Normal file
205
src/modules/poweraccent/PowerAccent.Core.UnitTests/PointTests.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace PowerAccent.Core.UnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class PointTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Product code: Point() default constructor
|
||||
/// What: Verifies X and Y initialize to zero
|
||||
/// Why: Default state must be deterministic — uninitialized coordinates cause misplacement
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DefaultConstructor_ShouldInitializeToZero()
|
||||
{
|
||||
var point = new Point();
|
||||
Assert.AreEqual(0, point.X);
|
||||
Assert.AreEqual(0, point.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point(double, double) constructor
|
||||
/// What: Verifies that fractional coordinates are preserved exactly
|
||||
/// Why: Sub-pixel precision is needed for DPI-aware positioning
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DoubleConstructor_ShouldSetCoordinates()
|
||||
{
|
||||
var point = new Point(3.5, 7.2);
|
||||
Assert.AreEqual(3.5, point.X);
|
||||
Assert.AreEqual(7.2, point.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point(int, int) constructor
|
||||
/// What: Verifies integer coordinates are stored as doubles without loss
|
||||
/// Why: Implicit int→double conversion must be exact for pixel-aligned positioning
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IntConstructor_ShouldSetCoordinates()
|
||||
{
|
||||
var point = new Point(10, 20);
|
||||
Assert.AreEqual(10.0, point.X);
|
||||
Assert.AreEqual(20.0, point.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point(System.Drawing.Point) constructor
|
||||
/// What: Verifies conversion from System.Drawing.Point preserves coordinates
|
||||
/// Why: Win32 API interop uses System.Drawing.Point — conversion must be lossless
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DrawingPointConstructor_ShouldConvertCoordinates()
|
||||
{
|
||||
var drawingPoint = new System.Drawing.Point(15, 25);
|
||||
var point = new Point(drawingPoint);
|
||||
Assert.AreEqual(15.0, point.X);
|
||||
Assert.AreEqual(25.0, point.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point implicit operator from System.Drawing.Point
|
||||
/// What: Verifies implicit conversion works at assignment sites
|
||||
/// Why: Enables seamless interop without explicit casts in calling code
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ImplicitConversion_FromDrawingPoint_ShouldWork()
|
||||
{
|
||||
Point point = new System.Drawing.Point(100, 200);
|
||||
Assert.AreEqual(100.0, point.X);
|
||||
Assert.AreEqual(200.0, point.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point operator /(Point, double)
|
||||
/// What: Verifies scalar division halves both coordinates
|
||||
/// Why: Core arithmetic for DPI normalization
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionByScalar_ShouldDivideCoordinates()
|
||||
{
|
||||
var point = new Point(10.0, 20.0);
|
||||
var result = point / 2.0;
|
||||
Assert.AreEqual(5.0, result.X);
|
||||
Assert.AreEqual(10.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point operator /(Point, double) with negative divisor
|
||||
/// What: Verifies negative divisor negates both coordinates
|
||||
/// Why: Ensures correct sign propagation in coordinate math
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionByScalar_WithNegativeDivider_ShouldNegateCoordinates()
|
||||
{
|
||||
var point = new Point(10.0, 20.0);
|
||||
var result = point / -2.0;
|
||||
Assert.AreEqual(-5.0, result.X);
|
||||
Assert.AreEqual(-10.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point operator /(Point, double) zero guard
|
||||
/// What: Verifies that dividing by zero throws DivideByZeroException
|
||||
/// Why: Prevents Infinity coordinates from corrupting toolbar placement
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionByScalar_WithZero_ShouldThrow()
|
||||
{
|
||||
var point = new Point(10.0, 20.0);
|
||||
_ = point / 0.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point operator /(Point, Point)
|
||||
/// What: Verifies component-wise division (X/X, Y/Y)
|
||||
/// Why: Used for coordinate space transformations
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionByPoint_ShouldDivideComponentwise()
|
||||
{
|
||||
var point = new Point(10.0, 20.0);
|
||||
var divider = new Point(2.0, 5.0);
|
||||
var result = point / divider;
|
||||
Assert.AreEqual(5.0, result.X);
|
||||
Assert.AreEqual(4.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point operator /(Point, Point) zero guard for X
|
||||
/// What: Verifies that a divider with X=0 throws DivideByZeroException
|
||||
/// Why: X=0 in a divider would produce Infinity for the X coordinate
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionByPoint_WithZeroX_ShouldThrow()
|
||||
{
|
||||
var point = new Point(10.0, 20.0);
|
||||
var divider = new Point(0.0, 5.0);
|
||||
_ = point / divider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point operator /(Point, Point) zero guard for Y
|
||||
/// What: Verifies that a divider with Y=0 throws DivideByZeroException
|
||||
/// Why: Y=0 in a divider would produce Infinity for the Y coordinate
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionByPoint_WithZeroY_ShouldThrow()
|
||||
{
|
||||
var point = new Point(10.0, 20.0);
|
||||
var divider = new Point(5.0, 0.0);
|
||||
_ = point / divider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point(double, double) constructor with negatives
|
||||
/// What: Verifies that negative coordinates are stored correctly
|
||||
/// Why: Negative coordinates are valid in multi-monitor setups (left/above primary)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NegativeCoordinates_ShouldBeAllowed()
|
||||
{
|
||||
var point = new Point(-5.5, -10.3);
|
||||
Assert.AreEqual(-5.5, point.X);
|
||||
Assert.AreEqual(-10.3, point.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point operator /(Point, double) with fractional divisor
|
||||
/// What: Verifies dividing by 0.5 effectively doubles coordinates
|
||||
/// Why: Fractional DPI values (1.25x, 1.5x) are common in production
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionByScalar_WithFractionalDivider_ShouldWork()
|
||||
{
|
||||
var point = new Point(10.0, 20.0);
|
||||
var result = point / 0.5;
|
||||
Assert.AreEqual(20.0, result.X);
|
||||
Assert.AreEqual(40.0, result.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Point operator /(Point, Point) zero guard for both X and Y
|
||||
/// What: Verifies that a divider with both X=0 and Y=0 throws
|
||||
/// Why: Degenerate origin-point divider must be rejected — tests the first guard hit
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionByPoint_WithBothZero_ShouldThrow()
|
||||
{
|
||||
var point = new Point(10.0, 20.0);
|
||||
var divider = new Point(0.0, 0.0);
|
||||
_ = point / divider;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\PowerAccent.Core.Tests\</OutputPath>
|
||||
<RootNamespace>PowerAccent.Core.UnitTests</RootNamespace>
|
||||
<AssemblyName>PowerToys.PowerAccent.Core.UnitTests</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PowerAccent.Core\PowerAccent.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
223
src/modules/poweraccent/PowerAccent.Core.UnitTests/RectTests.cs
Normal file
223
src/modules/poweraccent/PowerAccent.Core.UnitTests/RectTests.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace PowerAccent.Core.UnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class RectTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Product code: Rect() default constructor
|
||||
/// What: Verifies all properties initialize to zero
|
||||
/// Why: Default state must be well-defined to avoid uninitialized geometry bugs
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DefaultConstructor_ShouldInitializeToZero()
|
||||
{
|
||||
var rect = new Rect();
|
||||
Assert.AreEqual(0, rect.X);
|
||||
Assert.AreEqual(0, rect.Y);
|
||||
Assert.AreEqual(0, rect.Width);
|
||||
Assert.AreEqual(0, rect.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect(int, int, int, int) constructor
|
||||
/// What: Verifies that integer arguments are correctly stored as doubles
|
||||
/// Why: Implicit int→double conversion must preserve exact values
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IntConstructor_ShouldSetAllProperties()
|
||||
{
|
||||
var rect = new Rect(10, 20, 800, 600);
|
||||
Assert.AreEqual(10.0, rect.X);
|
||||
Assert.AreEqual(20.0, rect.Y);
|
||||
Assert.AreEqual(800.0, rect.Width);
|
||||
Assert.AreEqual(600.0, rect.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect(double, double, double, double) constructor
|
||||
/// What: Verifies that fractional double values are stored exactly
|
||||
/// Why: Ensures no rounding or truncation occurs during construction
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DoubleConstructor_ShouldSetAllProperties()
|
||||
{
|
||||
var rect = new Rect(1.5, 2.5, 100.3, 200.7);
|
||||
Assert.AreEqual(1.5, rect.X);
|
||||
Assert.AreEqual(2.5, rect.Y);
|
||||
Assert.AreEqual(100.3, rect.Width);
|
||||
Assert.AreEqual(200.7, rect.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect(Point, Size) constructor
|
||||
/// What: Verifies that Point and Size components map to correct Rect properties
|
||||
/// Why: X/Y come from Point, Width/Height from Size — a swap would silently corrupt layout
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void PointSizeConstructor_ShouldSetFromComponents()
|
||||
{
|
||||
var point = new Point(50.0, 100.0);
|
||||
var size = new Size(400.0, 300.0);
|
||||
var rect = new Rect(point, size);
|
||||
Assert.AreEqual(50.0, rect.X);
|
||||
Assert.AreEqual(100.0, rect.Y);
|
||||
Assert.AreEqual(400.0, rect.Width);
|
||||
Assert.AreEqual(300.0, rect.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect operator /(Rect, double)
|
||||
/// What: Verifies component-wise division by a scalar
|
||||
/// Why: Used for DPI scaling — incorrect division would misposition the toolbar
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionByScalar_ShouldDivideAllComponents()
|
||||
{
|
||||
var rect = new Rect(10.0, 20.0, 100.0, 200.0);
|
||||
var result = rect / 2.0;
|
||||
Assert.AreEqual(5.0, result.X);
|
||||
Assert.AreEqual(10.0, result.Y);
|
||||
Assert.AreEqual(50.0, result.Width);
|
||||
Assert.AreEqual(100.0, result.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect operator /(Rect, double) zero guard
|
||||
/// What: Verifies that dividing by zero throws DivideByZeroException
|
||||
/// Why: Prevents Infinity values from propagating through layout calculations
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionByScalar_WithZero_ShouldThrow()
|
||||
{
|
||||
var rect = new Rect(10.0, 20.0, 100.0, 200.0);
|
||||
_ = rect / 0.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect operator /(Rect, Rect)
|
||||
/// What: Verifies component-wise division (X/X, Y/Y, Width/Width, Height/Height)
|
||||
/// Why: Used for coordinate system transformations between screen and window space
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionByRect_ShouldDivideComponentwise()
|
||||
{
|
||||
var rect = new Rect(10.0, 20.0, 100.0, 200.0);
|
||||
var divider = new Rect(2.0, 5.0, 10.0, 20.0);
|
||||
var result = rect / divider;
|
||||
Assert.AreEqual(5.0, result.X);
|
||||
Assert.AreEqual(4.0, result.Y);
|
||||
Assert.AreEqual(10.0, result.Width);
|
||||
Assert.AreEqual(10.0, result.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect operator /(Rect, Rect) zero guard for X component
|
||||
/// What: Verifies that a divider with X=0 throws DivideByZeroException
|
||||
/// Why: X=0 in a divider Rect would produce Infinity for the X coordinate
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionByRect_WithZeroX_ShouldThrow()
|
||||
{
|
||||
var rect = new Rect(10.0, 20.0, 100.0, 200.0);
|
||||
var divider = new Rect(0.0, 5.0, 10.0, 20.0);
|
||||
_ = rect / divider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect operator /(Rect, Rect) zero guard for Y component
|
||||
/// What: Verifies that a divider with Y=0 throws DivideByZeroException
|
||||
/// Why: Y=0 in a divider Rect would produce Infinity for the Y coordinate
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionByRect_WithZeroY_ShouldThrow()
|
||||
{
|
||||
var rect = new Rect(10.0, 20.0, 100.0, 200.0);
|
||||
var divider = new Rect(5.0, 0.0, 10.0, 20.0);
|
||||
_ = rect / divider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect operator /(Rect, Rect) zero guard for Width component
|
||||
/// What: Verifies that a divider with Width=0 throws DivideByZeroException
|
||||
/// Why: Guards against producing Infinity values that propagate through calculations
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionByRect_WithZeroWidth_ShouldThrow()
|
||||
{
|
||||
var rect = new Rect(10, 20, 100, 200);
|
||||
var divider = new Rect(1, 1, 0, 1);
|
||||
_ = rect / divider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect operator /(Rect, Rect) zero guard for Height component
|
||||
/// What: Verifies that a divider with Height=0 throws DivideByZeroException
|
||||
/// Why: Guards against producing Infinity values that propagate through calculations
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionByRect_WithZeroHeight_ShouldThrow()
|
||||
{
|
||||
var rect = new Rect(10, 20, 100, 200);
|
||||
var divider = new Rect(1, 1, 1, 0);
|
||||
_ = rect / divider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect operator /(Rect, Rect) zero guard for all components
|
||||
/// What: Verifies that a divider with all zero components throws DivideByZeroException
|
||||
/// Why: Degenerate all-zero divider must be caught — tests the first guard hit (X=0)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionByRect_WithAllZeroComponents_ShouldThrow()
|
||||
{
|
||||
var rect = new Rect(10, 20, 100, 200);
|
||||
var divider = new Rect(0, 0, 0, 0);
|
||||
_ = rect / divider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect(double, double, double, double) constructor
|
||||
/// What: Verifies that negative X/Y coordinates are allowed
|
||||
/// Why: Multi-monitor setups can have negative screen coordinates (left/above primary)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void NegativeCoordinates_ShouldBeAllowed()
|
||||
{
|
||||
var rect = new Rect(-100.0, -200.0, 400.0, 300.0);
|
||||
Assert.AreEqual(-100.0, rect.X);
|
||||
Assert.AreEqual(-200.0, rect.Y);
|
||||
Assert.AreEqual(400.0, rect.Width);
|
||||
Assert.AreEqual(300.0, rect.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Rect operator /(Rect, double) with negative divisor
|
||||
/// What: Verifies that negative divisor negates all components
|
||||
/// Why: Ensures sign propagation is correct — used in coordinate flipping scenarios
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionByScalar_WithNegativeDivider_ShouldNegateComponents()
|
||||
{
|
||||
var rect = new Rect(10.0, 20.0, 100.0, 200.0);
|
||||
var result = rect / -1.0;
|
||||
Assert.AreEqual(-10.0, result.X);
|
||||
Assert.AreEqual(-20.0, result.Y);
|
||||
Assert.AreEqual(-100.0, result.Width);
|
||||
Assert.AreEqual(-200.0, result.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
192
src/modules/poweraccent/PowerAccent.Core.UnitTests/SizeTests.cs
Normal file
192
src/modules/poweraccent/PowerAccent.Core.UnitTests/SizeTests.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace PowerAccent.Core.UnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class SizeTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Product code: Size() default constructor
|
||||
/// What: Verifies Width and Height initialize to zero
|
||||
/// Why: Default state must be deterministic to avoid layout corruption
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DefaultConstructor_ShouldInitializeToZero()
|
||||
{
|
||||
var size = new Size();
|
||||
Assert.AreEqual(0, size.Width);
|
||||
Assert.AreEqual(0, size.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size(double, double) constructor
|
||||
/// What: Verifies fractional values are preserved exactly
|
||||
/// Why: Ensures no rounding during construction for sub-pixel precision
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DoubleConstructor_ShouldSetDimensions()
|
||||
{
|
||||
var size = new Size(100.5, 200.3);
|
||||
Assert.AreEqual(100.5, size.Width);
|
||||
Assert.AreEqual(200.3, size.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size(int, int) constructor
|
||||
/// What: Verifies integer dimensions are stored as doubles without loss
|
||||
/// Why: Implicit int→double conversion must be exact
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IntConstructor_ShouldSetDimensions()
|
||||
{
|
||||
var size = new Size(800, 600);
|
||||
Assert.AreEqual(800.0, size.Width);
|
||||
Assert.AreEqual(600.0, size.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size implicit operator from System.Drawing.Size
|
||||
/// What: Verifies implicit conversion from System.Drawing.Size works
|
||||
/// Why: Interop boundary — System.Drawing types are used by Win32 APIs
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ImplicitConversion_FromDrawingSize_ShouldWork()
|
||||
{
|
||||
Size size = new System.Drawing.Size(1920, 1080);
|
||||
Assert.AreEqual(1920.0, size.Width);
|
||||
Assert.AreEqual(1080.0, size.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size operator /(Size, double)
|
||||
/// What: Verifies scalar division halves both dimensions
|
||||
/// Why: Core arithmetic used for DPI scaling — must produce exact results
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionByScalar_ShouldDivideDimensions()
|
||||
{
|
||||
var size = new Size(100.0, 200.0);
|
||||
var result = size / 2.0;
|
||||
Assert.AreEqual(50.0, result.Width);
|
||||
Assert.AreEqual(100.0, result.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size operator /(Size, double) with fractional divisor
|
||||
/// What: Verifies that dividing by 0.5 effectively doubles dimensions
|
||||
/// Why: Fractional DPI values are real (e.g., 1.25x, 1.5x scaling)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionByScalar_WithFractionalDivider_ShouldWork()
|
||||
{
|
||||
var size = new Size(100.0, 200.0);
|
||||
var result = size / 0.5;
|
||||
Assert.AreEqual(200.0, result.Width);
|
||||
Assert.AreEqual(400.0, result.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size operator /(Size, double) zero guard
|
||||
/// What: Verifies that dividing by zero throws DivideByZeroException
|
||||
/// Why: Prevents Infinity dimensions that would corrupt window layout
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionByScalar_WithZero_ShouldThrow()
|
||||
{
|
||||
var size = new Size(100.0, 200.0);
|
||||
_ = size / 0.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size operator /(Size, Size)
|
||||
/// What: Verifies component-wise division (Width/Width, Height/Height)
|
||||
/// Why: Used for computing scaling ratios between two rectangles
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionBySize_ShouldDivideComponentwise()
|
||||
{
|
||||
var size = new Size(100.0, 200.0);
|
||||
var divider = new Size(10.0, 20.0);
|
||||
var result = size / divider;
|
||||
Assert.AreEqual(10.0, result.Width);
|
||||
Assert.AreEqual(10.0, result.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size operator /(Size, Size) zero Width guard
|
||||
/// What: Verifies that a divider with Width=0 throws DivideByZeroException
|
||||
/// Why: Width=0 divider would produce Infinity — must be caught early
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionBySize_WithZeroWidth_ShouldThrow()
|
||||
{
|
||||
var size = new Size(100.0, 200.0);
|
||||
var divider = new Size(0.0, 20.0);
|
||||
_ = size / divider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size operator /(Size, Size) zero Height guard
|
||||
/// What: Verifies that a divider with Height=0 throws DivideByZeroException
|
||||
/// Why: Height=0 divider would produce Infinity — must be caught early
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionBySize_WithZeroHeight_ShouldThrow()
|
||||
{
|
||||
var size = new Size(100.0, 200.0);
|
||||
var divider = new Size(10.0, 0.0);
|
||||
_ = size / divider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size operator /(Size, double) with negative divisor
|
||||
/// What: Verifies that negative divisor correctly negates both dimensions
|
||||
/// Why: Ensures sign propagation is correct in arithmetic
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionByScalar_WithNegativeDivider_ShouldWork()
|
||||
{
|
||||
var size = new Size(100.0, 200.0);
|
||||
var result = size / -2.0;
|
||||
Assert.AreEqual(-50.0, result.Width);
|
||||
Assert.AreEqual(-100.0, result.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size operator /(Size, double) with very small divisor
|
||||
/// What: Verifies division by a small number produces expected large result
|
||||
/// Why: Near-zero divisors (e.g., very low DPI) must not crash or produce NaN
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DivisionByScalar_WithVerySmallDivider_ShouldYieldLargeResult()
|
||||
{
|
||||
var size = new Size(1.0, 1.0);
|
||||
var result = size / 0.001;
|
||||
Assert.AreEqual(1000.0, result.Width, 0.01);
|
||||
Assert.AreEqual(1000.0, result.Height, 0.01);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product code: Size operator /(Size, Size) zero guard for both dimensions
|
||||
/// What: Verifies that a divider with both Width=0 and Height=0 throws
|
||||
/// Why: Degenerate zero-size divider must be rejected — tests the first guard hit
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(DivideByZeroException))]
|
||||
public void DivisionBySize_WithBothZeroDimensions_ShouldThrow()
|
||||
{
|
||||
var size = new Size(100.0, 200.0);
|
||||
var divider = new Size(0.0, 0.0);
|
||||
_ = size / divider;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public struct Rect
|
||||
|
||||
public static Rect operator /(Rect rect, Rect divider)
|
||||
{
|
||||
if (divider.X == 0 || divider.Y == 0)
|
||||
if (divider.X == 0 || divider.Y == 0 || divider.Width == 0 || divider.Height == 0)
|
||||
{
|
||||
throw new DivideByZeroException();
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public struct Size
|
||||
|
||||
public static Size operator /(Size size, Size divider)
|
||||
{
|
||||
if (divider.Width == 0 || divider.Height == 0 || divider.Width == 0 || divider.Height == 0)
|
||||
if (divider.Width == 0 || divider.Height == 0)
|
||||
{
|
||||
throw new DivideByZeroException();
|
||||
}
|
||||
|
||||
@@ -33,4 +33,10 @@
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
<ProjectReference Include="..\PowerAccentKeyboardService\PowerAccentKeyboardService.vcxproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
|
||||
<_Parameter1>PowerToys.PowerAccent.Core.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -28,11 +28,7 @@
|
||||
so it doesn't conflict with the dll coming from .NET SDK. -->
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Diagnostics.EventLog">
|
||||
<!-- This package is a transitive dependency, but we need to set it here so we can exclude the assets,
|
||||
so it doesn't conflict with the dll coming from .NET SDK. -->
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
</PackageReference>
|
||||
<!-- System.Diagnostics.EventLog is now provided to all C# projects via Directory.Build.props -->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user