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 + +