diff --git a/EarTrumpet.Tests/EarTrumpet.Tests.csproj b/EarTrumpet.Tests/EarTrumpet.Tests.csproj new file mode 100644 index 000000000..8cb2e8ebc --- /dev/null +++ b/EarTrumpet.Tests/EarTrumpet.Tests.csproj @@ -0,0 +1,20 @@ + + + net8.0-windows10.0.19041.0 + true + false + x64 + x64 + win-x64 + false + ..\Build\Tests\ + + + + + + + + + + diff --git a/EarTrumpet.Tests/LocalizationTests.cs b/EarTrumpet.Tests/LocalizationTests.cs new file mode 100644 index 000000000..a110cc2d6 --- /dev/null +++ b/EarTrumpet.Tests/LocalizationTests.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Xunit; + +namespace EarTrumpet.Tests; + +// Guards the "no hardcoded text" rule for the settings UI: every resource XAML points at must exist, +// and the strings this redesign introduced must be translated for the two supported languages. +public class LocalizationTests +{ + private static readonly string s_project = FindProjectDir(); + + private static string FindProjectDir() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null && !File.Exists(Path.Combine(dir.FullName, "EarTrumpet", "EarTrumpet.csproj"))) + { + dir = dir.Parent; + } + return Path.Combine(dir?.FullName ?? throw new InvalidOperationException("repo root not found"), "EarTrumpet"); + } + + private static string[] Keys(string resx) + => XDocument.Load(Path.Combine(s_project, "Properties", resx)).Root.Elements("data").Select(e => (string)e.Attribute("name")).ToArray(); + + private static string[] XamlResourceRefs() + => Directory.GetFiles(s_project, "*.xaml", SearchOption.AllDirectories) + .Where(f => !f.Contains(Path.DirectorySeparatorChar + "obj" + Path.DirectorySeparatorChar)) + .SelectMany(f => Regex.Matches(File.ReadAllText(f), @"resx:Resources\.(\w+)").Select(m => m.Groups[1].Value)) + .Distinct().ToArray(); + + [Fact] + public void EveryXamlResourceExistsInDefaultResx() + { + var keys = Keys("Resources.resx"); + Assert.Empty(XamlResourceRefs().Except(keys)); + } + + [Fact] + public void EveryDesignerPropertyExistsInDefaultResx() + { + var designer = File.ReadAllText(Path.Combine(s_project, "Properties", "Resources.Designer.cs")); + var props = Regex.Matches(designer, @"public static string (\w+) \{").Select(m => m.Groups[1].Value); + Assert.Empty(props.Except(Keys("Resources.resx"))); + } + + [Fact] + public void SettingsRedesignStringsAreTranslatedToSpanish() + { + var es = Keys("Resources.es-ES.resx"); + var redesign = Keys("Resources.resx").Where(k => + k == "GeneralSettingsPageText" || k.StartsWith("SettingsSection") || + (k.StartsWith("Settings") && (k.EndsWith("Description") || k.Contains("Volume") || k.Contains("Snap") || + k.Contains("ScrollWheel") || k.Contains("Translucent") || k.Contains("Legacy")))).ToArray(); + Assert.NotEmpty(redesign); + Assert.Empty(redesign.Except(es)); + } +} diff --git a/EarTrumpet.Tests/VolumeMathTests.cs b/EarTrumpet.Tests/VolumeMathTests.cs new file mode 100644 index 000000000..c9c3496e5 --- /dev/null +++ b/EarTrumpet.Tests/VolumeMathTests.cs @@ -0,0 +1,68 @@ +using EarTrumpet.DataModel; +using EarTrumpet.Extensions; +using EarTrumpet.UI.Helpers; +using Xunit; + +namespace EarTrumpet.Tests; + +public class VolumeMathTests +{ + [Theory] + [InlineData(0, 5, 5)] + [InlineData(3, 5, 5)] + [InlineData(5, 5, 10)] + [InlineData(98, 5, 100)] + [InlineData(100, 5, 100)] + public void NextSnapPoint_LandsOnGridAboveAndNeverExceeds100(double current, int step, double expected) + => Assert.Equal(expected, VolumeStepper.NextSnapPoint(current, step)); + + [Theory] + [InlineData(100, 5, 95)] + [InlineData(7, 5, 5)] + [InlineData(5, 5, 0)] + [InlineData(2, 5, 0)] + [InlineData(0, 5, 0)] + public void PreviousSnapPoint_LandsOnGridBelowAndNeverGoesNegative(double current, int step, double expected) + => Assert.Equal(expected, VolumeStepper.PreviousSnapPoint(current, step)); + + [Theory] + [InlineData(12, 5, 10)] + [InlineData(13, 5, 15)] + [InlineData(99.6, 3, 100)] // 100 stays reachable when it is not on the grid + [InlineData(1, 3, 0)] + public void NearestSnapPoint_RoundsToGridButKeepsFullVolumeReachable(double current, int step, double expected) + => Assert.Equal(expected, VolumeStepper.NearestSnapPoint(current, step)); + + [Fact] + public void Bound_ClampsBothEnds() + { + Assert.Equal(0d, (-5d).Bound(0, 1)); + Assert.Equal(1d, 5d.Bound(0, 1)); + Assert.Equal(0.5d, 0.5d.Bound(0, 1)); + } + + [Theory] + [InlineData(1.0)] + [InlineData(0.5)] + [InlineData(0.01)] + public void LogarithmicConversion_RoundTrips(double linear) + => Assert.Equal(linear, linear.LinearToLog().LogToLinear(), 9); + + [Fact] + public void FullVolumeIs0Db() + => Assert.Equal(0d, 1d.LinearToLog(), 9); + + [Fact] + public void ForceOpaque_DisablesTransparencyRegardlessOfWindows() + { + try + { + SystemSettings.ForceOpaque = true; + Assert.False(SystemSettings.IsTransparencyEnabled); + } + finally + { + SystemSettings.ForceOpaque = false; + } + } +} diff --git a/EarTrumpet.sln b/EarTrumpet.sln index 873269e34..f4584c5f1 100644 --- a/EarTrumpet.sln +++ b/EarTrumpet.sln @@ -25,6 +25,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chocolatey", "Chocolatey", EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "EarTrumpet.Package", "EarTrumpet.Package\EarTrumpet.Package.wapproj", "{EA5510ED-F014-4587-A505-64C59D5B2627}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EarTrumpet.Tests", "EarTrumpet.Tests\EarTrumpet.Tests.csproj", "{BA949B87-C0DF-4986-99CB-30458590F3FF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -83,6 +85,24 @@ Global {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x86.ActiveCfg = Debug|x86 {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x86.Build.0 = Debug|x86 {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x86.Deploy.0 = Debug|x86 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Debug|ARM64.ActiveCfg = Debug|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Debug|ARM64.Build.0 = Debug|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Debug|x64.ActiveCfg = Debug|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Debug|x64.Build.0 = Debug|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Debug|x86.ActiveCfg = Debug|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Debug|x86.Build.0 = Debug|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Release|ARM64.ActiveCfg = Release|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Release|ARM64.Build.0 = Release|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Release|x64.ActiveCfg = Release|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Release|x64.Build.0 = Release|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Release|x86.ActiveCfg = Release|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.Release|x86.Build.0 = Release|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.VSDebug|ARM64.ActiveCfg = Debug|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.VSDebug|ARM64.Build.0 = Debug|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.VSDebug|x64.ActiveCfg = Debug|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.VSDebug|x64.Build.0 = Debug|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.VSDebug|x86.ActiveCfg = Debug|x64 + {BA949B87-C0DF-4986-99CB-30458590F3FF}.VSDebug|x86.Build.0 = Debug|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ActionsCategoryViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ActionsCategoryViewModel.cs index 358b16c78..f07866a80 100644 --- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ActionsCategoryViewModel.cs +++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ActionsCategoryViewModel.cs @@ -40,11 +40,6 @@ public ActionsCategoryViewModel() Glyph = "\xE948", GlyphFontSize = 15, } ]; - - if (Pages.Count == 2) - { - Toolbar[0].Command.Execute(null); - } } internal void ReloadSavedPages() diff --git a/EarTrumpet/App.xaml b/EarTrumpet/App.xaml index fb57d8fd4..3988fa609 100644 --- a/EarTrumpet/App.xaml +++ b/EarTrumpet/App.xaml @@ -588,20 +588,21 @@ - + - + - + BorderThickness="1" + CornerRadius="8"> @@ -611,12 +612,12 @@ - + - + @@ -626,7 +627,7 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + @@ -410,13 +361,15 @@ - + + + +