diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt
index b9be3f82a47d..8dcfa443f283 100644
--- a/.github/actions/spell-check/allow/code.txt
+++ b/.github/actions/spell-check/allow/code.txt
@@ -328,6 +328,9 @@ MRUCMPPROC
MRUINFO
REGSTR
+# Quick Accent
+Ene
+
# Misc Win32 APIs and PInvokes
INVOKEIDLIST
MEMORYSTATUSEX
@@ -365,5 +368,9 @@ nostdin
engtype
Nonpaged
+# Spell-check fragments
+traies
+udit
+
# XAML
Untargeted
diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index da93694080fd..24ef88dcc0ef 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -776,7 +776,6 @@ lowlevel
LOWORD
lparam
LPBITMAPINFOHEADER
-LPCFHOOKPROC
lpch
LPCITEMIDLIST
LPCLSID
@@ -818,7 +817,6 @@ lstrlen
LTEXT
LTRREADING
luid
-LUMA
lusrmgr
LVal
LWA
@@ -837,7 +835,6 @@ MAPPEDTOSAMEKEY
MAPTOSAMESHORTCUT
MAPVK
MARKDOWNPREVIEWHANDLERCPP
-MAXDWORD
MAXSHORTCUTSIZE
maxversiontested
MBM
@@ -899,7 +896,6 @@ MOUSEINPUT
MOVESIZEEND
MOVESIZESTART
MRM
-MRT
mru
msc
mscorlib
@@ -2264,7 +2260,6 @@ virama
vnd
vredraw
VSpeed
-VSync
WASDK
WCRAPI
wft
diff --git a/PowerToys.slnx b/PowerToys.slnx
index 9e14ce1a6c41..ee3e7d7bc73d 100644
--- a/PowerToys.slnx
+++ b/PowerToys.slnx
@@ -789,6 +789,10 @@
+
+
+
+
@@ -801,6 +805,10 @@
+
+
+
+
diff --git a/src/modules/peek/Peek.Common.UnitTests/MathHelperTests.cs b/src/modules/peek/Peek.Common.UnitTests/MathHelperTests.cs
new file mode 100644
index 000000000000..3a8c9c37468a
--- /dev/null
+++ b/src/modules/peek/Peek.Common.UnitTests/MathHelperTests.cs
@@ -0,0 +1,152 @@
+// 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 MathHelperTests
+ {
+ [TestMethod]
+ public void Modulo_PositiveNumbers_ShouldReturnStandardModulo()
+ {
+ Assert.AreEqual(1, MathHelper.Modulo(7, 3));
+ }
+
+ [TestMethod]
+ public void Modulo_ZeroDividend_ShouldReturnZero()
+ {
+ Assert.AreEqual(0, MathHelper.Modulo(0, 5));
+ }
+
+ [TestMethod]
+ public void Modulo_ExactDivision_ShouldReturnZero()
+ {
+ Assert.AreEqual(0, MathHelper.Modulo(6, 3));
+ }
+
+ [TestMethod]
+ public void Modulo_NegativeDividend_ShouldReturnPositiveResult()
+ {
+ // -1 % 3 in C# returns -1, but Modulo should return 2
+ Assert.AreEqual(2, MathHelper.Modulo(-1, 3));
+ }
+
+ [TestMethod]
+ public void Modulo_NegativeDividend_LargerMagnitude_ShouldWrapCorrectly()
+ {
+ // -7 % 3: C# gives -1; proper modulo: (-7 % 3 + 3) % 3 = (-1 + 3) % 3 = 2
+ Assert.AreEqual(2, MathHelper.Modulo(-7, 3));
+ }
+
+ [TestMethod]
+ public void Modulo_NegativeDividend_ExactMultiple_ShouldReturnZero()
+ {
+ // -6 % 3 = 0
+ Assert.AreEqual(0, MathHelper.Modulo(-6, 3));
+ }
+
+ [TestMethod]
+ public void Modulo_PositiveDividend_DivisorOne_ShouldReturnZero()
+ {
+ Assert.AreEqual(0, MathHelper.Modulo(5, 1));
+ }
+
+ [TestMethod]
+ public void Modulo_NegativeDividend_DivisorOne_ShouldReturnZero()
+ {
+ Assert.AreEqual(0, MathHelper.Modulo(-5, 1));
+ }
+
+ [TestMethod]
+ public void Modulo_LargePositiveNumbers_ShouldWork()
+ {
+ Assert.AreEqual(1, MathHelper.Modulo(1000001, 1000000));
+ }
+
+ [TestMethod]
+ public void Modulo_DividendLessThanDivisor_ShouldReturnDividend()
+ {
+ Assert.AreEqual(2, MathHelper.Modulo(2, 5));
+ }
+
+ [TestMethod]
+ public void Modulo_NegativeDividend_MinusTwo_ModThree_ShouldReturnOne()
+ {
+ Assert.AreEqual(1, MathHelper.Modulo(-2, 3));
+ }
+
+ [TestMethod]
+ public void NumberOfDigits_Zero_ShouldReturnOne()
+ {
+ Assert.AreEqual(1, MathHelper.NumberOfDigits(0));
+ }
+
+ [TestMethod]
+ public void NumberOfDigits_SingleDigit_ShouldReturnOne()
+ {
+ Assert.AreEqual(1, MathHelper.NumberOfDigits(5));
+ }
+
+ [TestMethod]
+ public void NumberOfDigits_TwoDigits_ShouldReturnTwo()
+ {
+ Assert.AreEqual(2, MathHelper.NumberOfDigits(42));
+ }
+
+ [TestMethod]
+ public void NumberOfDigits_ThreeDigits_ShouldReturnThree()
+ {
+ Assert.AreEqual(3, MathHelper.NumberOfDigits(100));
+ }
+
+ [TestMethod]
+ public void NumberOfDigits_NegativeNumber_ShouldIgnoreSign()
+ {
+ Assert.AreEqual(3, MathHelper.NumberOfDigits(-123));
+ }
+
+ [TestMethod]
+ public void NumberOfDigits_MaxValue_ShouldReturnTenDigits()
+ {
+ // int.MaxValue = 2147483647, which has 10 digits
+ Assert.AreEqual(10, MathHelper.NumberOfDigits(int.MaxValue));
+ }
+
+ [TestMethod]
+ public void NumberOfDigits_MinValue_ShouldHandleAbsoluteValue()
+ {
+ // int.MinValue = -2147483648, Math.Abs would overflow but ToString handles it
+ // The implementation uses Math.Abs(num).ToString().Length
+ // For int.MinValue, Math.Abs throws - test the boundary just above it
+ Assert.AreEqual(10, MathHelper.NumberOfDigits(int.MinValue + 1));
+ }
+
+ [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));
+ }
+
+ [TestMethod]
+ public void NumberOfDigits_BoundaryValues_NineAndTen()
+ {
+ Assert.AreEqual(1, MathHelper.NumberOfDigits(9));
+ Assert.AreEqual(2, MathHelper.NumberOfDigits(10));
+ }
+
+ [TestMethod]
+ public void NumberOfDigits_BoundaryValues_NinetyNineAndHundred()
+ {
+ Assert.AreEqual(2, MathHelper.NumberOfDigits(99));
+ Assert.AreEqual(3, MathHelper.NumberOfDigits(100));
+ }
+ }
+}
diff --git a/src/modules/peek/Peek.Common.UnitTests/PathHelperTests.cs b/src/modules/peek/Peek.Common.UnitTests/PathHelperTests.cs
new file mode 100644
index 000000000000..de3c3626d216
--- /dev/null
+++ b/src/modules/peek/Peek.Common.UnitTests/PathHelperTests.cs
@@ -0,0 +1,86 @@
+// 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
+ {
+ [TestMethod]
+ public void IsUncPath_StandardUncPath_ShouldReturnTrue()
+ {
+ Assert.IsTrue(PathHelper.IsUncPath(@"\\server\share"));
+ }
+
+ [TestMethod]
+ public void IsUncPath_UncPathWithSubfolder_ShouldReturnTrue()
+ {
+ Assert.IsTrue(PathHelper.IsUncPath(@"\\server\share\folder\file.txt"));
+ }
+
+ [TestMethod]
+ public void IsUncPath_UncPathWithDottedServer_ShouldReturnTrue()
+ {
+ Assert.IsTrue(PathHelper.IsUncPath(@"\\server.domain.com\share"));
+ }
+
+ [TestMethod]
+ public void IsUncPath_UncPathWithIPAddress_ShouldReturnTrue()
+ {
+ Assert.IsTrue(PathHelper.IsUncPath(@"\\192.168.1.1\share"));
+ }
+
+ [TestMethod]
+ public void IsUncPath_LocalDrivePath_ShouldReturnFalse()
+ {
+ Assert.IsFalse(PathHelper.IsUncPath(@"C:\Users\test\file.txt"));
+ }
+
+ [TestMethod]
+ public void IsUncPath_LocalRootPath_ShouldReturnFalse()
+ {
+ Assert.IsFalse(PathHelper.IsUncPath(@"D:\"));
+ }
+
+ [TestMethod]
+ public void IsUncPath_RelativePath_ShouldReturnFalse()
+ {
+ Assert.IsFalse(PathHelper.IsUncPath(@"folder\file.txt"));
+ }
+
+ [TestMethod]
+ public void IsUncPath_EmptyString_ShouldReturnFalse()
+ {
+ Assert.IsFalse(PathHelper.IsUncPath(string.Empty));
+ }
+
+ [TestMethod]
+ public void IsUncPath_HttpUrl_ShouldReturnFalse()
+ {
+ Assert.IsFalse(PathHelper.IsUncPath("http://example.com/path"));
+ }
+
+ [TestMethod]
+ public void IsUncPath_FileUri_ShouldReturnFalse()
+ {
+ Assert.IsFalse(PathHelper.IsUncPath("file:///C:/Users/test/file.txt"));
+ }
+
+ [TestMethod]
+ public void IsUncPath_UncFileUri_ShouldReturnTrue()
+ {
+ // file://server/share is a UNC URI
+ Assert.IsTrue(PathHelper.IsUncPath(@"\\server\share\file.txt"));
+ }
+
+ [TestMethod]
+ public void IsUncPath_SingleBackslash_ShouldReturnFalse()
+ {
+ Assert.IsFalse(PathHelper.IsUncPath(@"\server\share"));
+ }
+ }
+}
diff --git a/src/modules/peek/Peek.Common.UnitTests/Peek.Common.UnitTests.csproj b/src/modules/peek/Peek.Common.UnitTests/Peek.Common.UnitTests.csproj
new file mode 100644
index 000000000000..1acc0483659c
--- /dev/null
+++ b/src/modules/peek/Peek.Common.UnitTests/Peek.Common.UnitTests.csproj
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+ false
+ false
+ false
+ $(SolutionDir)$(Platform)\$(Configuration)\tests\Peek.Common.Tests\
+ Peek.Common.UnitTests
+ PowerToys.Peek.Common.UnitTests
+ Exe
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/poweraccent/PowerAccent.Core.UnitTests/CalculationTests.cs b/src/modules/poweraccent/PowerAccent.Core.UnitTests/CalculationTests.cs
new file mode 100644
index 000000000000..b9607249feae
--- /dev/null
+++ b/src/modules/poweraccent/PowerAccent.Core.UnitTests/CalculationTests.cs
@@ -0,0 +1,261 @@
+// 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 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);
+
+ [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);
+ }
+
+ [TestMethod]
+ public void GetRawCoordinatesFromCaret_NearLeftEdge_ShouldClampToScreenLeft()
+ {
+ // Caret near left edge - toolbar would extend past screen left
+ 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);
+ }
+
+ [TestMethod]
+ public void GetRawCoordinatesFromCaret_NearRightEdge_ShouldClampToScreenRight()
+ {
+ // Caret near right edge - toolbar would extend past screen right
+ 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);
+ }
+
+ [TestMethod]
+ public void GetRawCoordinatesFromCaret_NearTopEdge_ShouldPlaceBelow()
+ {
+ // Caret near top edge - toolbar above would go off-screen
+ 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);
+ }
+
+ [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);
+ }
+
+ [TestMethod]
+ public void GetRawCoordinatesFromCaret_OffsetScreen_ShouldRespectScreenOrigin()
+ {
+ // Simulate a second monitor offset at position (1920, 0)
+ 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);
+ }
+
+ [TestMethod]
+ public void GetRawCoordinatesFromCaret_ExactBoundary_ShouldNotClamp()
+ {
+ // Caret positioned exactly so toolbar fits perfectly
+ 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
+ // left + window.Width = 0 + 300 = 300, which <= 1920, so no right clamp
+ Assert.AreEqual(0.0, result.X);
+
+ // top = 100 - 50 - 20 = 30, which >= 0
+ Assert.AreEqual(30.0, result.Y);
+ }
+
+ [TestMethod]
+ [DataRow(Position.Top)]
+ [DataRow(Position.Bottom)]
+ [DataRow(Position.Left)]
+ [DataRow(Position.Right)]
+ [DataRow(Position.TopLeft)]
+ [DataRow(Position.TopRight)]
+ [DataRow(Position.BottomLeft)]
+ [DataRow(Position.BottomRight)]
+ [DataRow(Position.Center)]
+ public void GetRawCoordinatesFromPosition_AllPositions_ShouldNotThrow(Position position)
+ {
+ var result = Calculation.GetRawCoordinatesFromPosition(position, StandardScreen, ToolbarWindow, 1.0);
+
+ // Should return a valid point within reasonable range
+ Assert.IsTrue(!double.IsNaN(result.X));
+ Assert.IsTrue(!double.IsNaN(result.Y));
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+ }
+}
diff --git a/src/modules/poweraccent/PowerAccent.Core.UnitTests/LanguagesTests.cs b/src/modules/poweraccent/PowerAccent.Core.UnitTests/LanguagesTests.cs
new file mode 100644
index 000000000000..fa454175566c
--- /dev/null
+++ b/src/modules/poweraccent/PowerAccent.Core.UnitTests/LanguagesTests.cs
@@ -0,0 +1,186 @@
+// 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
+ {
+ [TestMethod]
+ public void GetDefaultLetterKey_EmptyLanguageArray_ShouldReturnEmpty()
+ {
+ var result = Languages.GetDefaultLetterKey(LetterKey.VK_A, Array.Empty());
+ Assert.AreEqual(0, result.Length);
+ }
+
+ [TestMethod]
+ public void GetDefaultLetterKey_SingleLanguage_ShouldReturnNonEmpty()
+ {
+ // French has accent characters for 'A'
+ var result = Languages.GetDefaultLetterKey(LetterKey.VK_A, new[] { Language.FR });
+ Assert.IsTrue(result.Length > 0, "French should have accented characters for A");
+ }
+
+ [TestMethod]
+ public void GetDefaultLetterKey_MultipleLanguages_ShouldMergeAndDeduplicate()
+ {
+ // Both French and Spanish have accented A characters, should be merged and deduplicated
+ 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 });
+
+ // Combined should have no duplicates
+ Assert.AreEqual(combinedResult.Length, combinedResult.Distinct().Count(), "Combined result should contain no duplicates");
+
+ // Combined should contain all characters from both languages
+ 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}'");
+ }
+ }
+
+ [TestMethod]
+ public void GetDefaultLetterKey_French_A_ShouldContainExpectedAccents()
+ {
+ var result = Languages.GetDefaultLetterKey(LetterKey.VK_A, new[] { Language.FR });
+
+ // French 'a' accents: à, â, æ, á, ä are common French accent characters for A
+ CollectionAssert.Contains(result, "à", "French should contain à");
+ CollectionAssert.Contains(result, "â", "French should contain â");
+ CollectionAssert.Contains(result, "æ", "French should contain æ");
+ }
+
+ [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");
+ }
+
+ [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");
+ }
+
+ [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");
+ }
+
+ [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");
+ }
+
+ [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");
+ }
+
+ [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");
+ }
+
+ [TestMethod]
+ public void GetDefaultLetterKey_Vietnamese_A_ShouldContainMultipleAccents()
+ {
+ var result = Languages.GetDefaultLetterKey(LetterKey.VK_A, new[] { Language.VI });
+
+ // Vietnamese has many A variants
+ 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 â");
+ }
+
+ [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");
+ }
+
+ [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 ¹");
+ }
+
+ [TestMethod]
+ public void GetDefaultLetterKey_Currency_ShouldReturnCurrencySymbols()
+ {
+ // Test that the Currency language returns currency symbols for relevant keys
+ var result = Languages.GetDefaultLetterKey(LetterKey.VK_R, new[] { Language.CUR });
+
+ // CUR (Currency) for R: ₹ (Indian Rupee) and others if present
+ // Even if empty for VK_R, it shouldn't throw
+ Assert.IsNotNull(result);
+ }
+
+ [TestMethod]
+ public void GetDefaultLetterKey_UnusedKeyForLanguage_ShouldReturnEmpty()
+ {
+ // German typically has no accent for 'Q'
+ var result = Languages.GetDefaultLetterKey(LetterKey.VK_Q, new[] { Language.DE });
+ Assert.AreEqual(0, result.Length, "German should have no accented characters for Q");
+ }
+
+ [TestMethod]
+ public void GetDefaultLetterKey_AllLanguages_ShouldReturnCachedResults()
+ {
+ var allLanguages = Enum.GetValues();
+
+ // First call computes and caches
+ var result1 = Languages.GetDefaultLetterKey(LetterKey.VK_A, allLanguages);
+
+ // Second call should return same array from cache
+ 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");
+ }
+
+ [TestMethod]
+ public void GetDefaultLetterKey_AllLanguages_EachLetterKey_ShouldNotThrow()
+ {
+ var allLanguages = Enum.GetValues();
+ var allLetterKeys = Enum.GetValues();
+
+ foreach (var letterKey in allLetterKeys)
+ {
+ var result = Languages.GetDefaultLetterKey(letterKey, allLanguages);
+ Assert.IsNotNull(result, $"Result for {letterKey} should not be null");
+
+ // Verify no duplicates
+ Assert.AreEqual(result.Length, result.Distinct().Count(), $"Result for {letterKey} should have no duplicates");
+ }
+ }
+ }
+}
diff --git a/src/modules/poweraccent/PowerAccent.Core.UnitTests/PointTests.cs b/src/modules/poweraccent/PowerAccent.Core.UnitTests/PointTests.cs
new file mode 100644
index 000000000000..5f39babde0b5
--- /dev/null
+++ b/src/modules/poweraccent/PowerAccent.Core.UnitTests/PointTests.cs
@@ -0,0 +1,126 @@
+// 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
+ {
+ [TestMethod]
+ public void DefaultConstructor_ShouldInitializeToZero()
+ {
+ var point = new Point();
+ Assert.AreEqual(0, point.X);
+ Assert.AreEqual(0, point.Y);
+ }
+
+ [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);
+ }
+
+ [TestMethod]
+ public void IntConstructor_ShouldSetCoordinates()
+ {
+ var point = new Point(10, 20);
+ Assert.AreEqual(10.0, point.X);
+ Assert.AreEqual(20.0, point.Y);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(DivideByZeroException))]
+ public void DivisionByScalar_WithZero_ShouldThrow()
+ {
+ var point = new Point(10.0, 20.0);
+ _ = point / 0.0;
+ }
+
+ [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);
+ }
+
+ [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;
+ }
+
+ [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;
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+ }
+}
diff --git a/src/modules/poweraccent/PowerAccent.Core.UnitTests/PowerAccent.Core.UnitTests.csproj b/src/modules/poweraccent/PowerAccent.Core.UnitTests/PowerAccent.Core.UnitTests.csproj
new file mode 100644
index 000000000000..7ae3077dce16
--- /dev/null
+++ b/src/modules/poweraccent/PowerAccent.Core.UnitTests/PowerAccent.Core.UnitTests.csproj
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+ false
+ false
+ false
+ $(SolutionDir)$(Platform)\$(Configuration)\tests\PowerAccent.Core.Tests\
+ PowerAccent.Core.UnitTests
+ PowerToys.PowerAccent.Core.UnitTests
+ Exe
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/poweraccent/PowerAccent.Core.UnitTests/RectTests.cs b/src/modules/poweraccent/PowerAccent.Core.UnitTests/RectTests.cs
new file mode 100644
index 000000000000..685fd86af77c
--- /dev/null
+++ b/src/modules/poweraccent/PowerAccent.Core.UnitTests/RectTests.cs
@@ -0,0 +1,126 @@
+// 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
+ {
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(DivideByZeroException))]
+ public void DivisionByScalar_WithZero_ShouldThrow()
+ {
+ var rect = new Rect(10.0, 20.0, 100.0, 200.0);
+ _ = rect / 0.0;
+ }
+
+ [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);
+ }
+
+ [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;
+ }
+
+ [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;
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+ }
+}
diff --git a/src/modules/poweraccent/PowerAccent.Core.UnitTests/SizeTests.cs b/src/modules/poweraccent/PowerAccent.Core.UnitTests/SizeTests.cs
new file mode 100644
index 000000000000..50e95cb472b5
--- /dev/null
+++ b/src/modules/poweraccent/PowerAccent.Core.UnitTests/SizeTests.cs
@@ -0,0 +1,118 @@
+// 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
+ {
+ [TestMethod]
+ public void DefaultConstructor_ShouldInitializeToZero()
+ {
+ var size = new Size();
+ Assert.AreEqual(0, size.Width);
+ Assert.AreEqual(0, size.Height);
+ }
+
+ [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);
+ }
+
+ [TestMethod]
+ public void IntConstructor_ShouldSetDimensions()
+ {
+ var size = new Size(800, 600);
+ Assert.AreEqual(800.0, size.Width);
+ Assert.AreEqual(600.0, size.Height);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(DivideByZeroException))]
+ public void DivisionByScalar_WithZero_ShouldThrow()
+ {
+ var size = new Size(100.0, 200.0);
+ _ = size / 0.0;
+ }
+
+ [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);
+ }
+
+ [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;
+ }
+
+ [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;
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+ }
+}
diff --git a/src/modules/poweraccent/PowerAccent.Core/PowerAccent.Core.csproj b/src/modules/poweraccent/PowerAccent.Core/PowerAccent.Core.csproj
index 49ae53eb2301..8e654eae87b7 100644
--- a/src/modules/poweraccent/PowerAccent.Core/PowerAccent.Core.csproj
+++ b/src/modules/poweraccent/PowerAccent.Core/PowerAccent.Core.csproj
@@ -33,4 +33,10 @@
+
+
+
+ <_Parameter1>PowerToys.PowerAccent.Core.UnitTests
+
+