diff --git a/ILSpy.Tests/Options/DisplayFontSizeTests.cs b/ILSpy.Tests/Options/DisplayFontSizeTests.cs new file mode 100644 index 000000000..5097184ef --- /dev/null +++ b/ILSpy.Tests/Options/DisplayFontSizeTests.cs @@ -0,0 +1,216 @@ +// Copyright (c) 2026 Christoph Wille +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Avalonia.Controls; +using Avalonia.Headless.NUnit; +using Avalonia.Threading; + +using AwesomeAssertions; + +using ICSharpCode.ILSpy.AppEnv; +using ICSharpCode.ILSpy.Commands; +using ICSharpCode.ILSpy.Docking; +using ICSharpCode.ILSpy.Options; +using ICSharpCode.ILSpy.Options.Panels; +using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpy.ViewModels; +using ICSharpCode.ILSpy.Views; + +using NUnit.Framework; + +namespace ICSharpCode.ILSpy.Tests; + +/// +/// The options dialog edits the font size in points (like the Windows font dialogs and the +/// WPF host's FontSizeConverter), while keeps +/// storing device-independent pixels so persisted settings round-trip with ILSpy 9.x. +/// The pt/px conversion lives in . +/// +[TestFixture] +public class DisplayFontSizeTests +{ + static (DisplaySettingsViewModel page, DisplaySettings settings) CreatePage() + { + var service = AppComposition.Current.GetExport(); + var page = new DisplaySettingsViewModel(); + page.Load(service); + return (page, service.DisplaySettings); + } + + [AvaloniaTest] + public void Default_Pixel_Size_Displays_As_10_Points() + { + var (page, settings) = CreatePage(); + var original = settings.SelectedFontSize; + try + { + settings.SelectedFontSize = 10.0 * 4 / 3; + page.SelectedFontSizePoints.Should().Be("10", + "the default 13.33 px must be presented as the 10 pt it actually is"); + } + finally + { + settings.SelectedFontSize = original; + } + } + + [AvaloniaTest] + public void Typed_Point_Size_Is_Stored_As_Pixels() + { + var (page, settings) = CreatePage(); + var original = settings.SelectedFontSize; + try + { + page.SelectedFontSizePoints = "12"; + settings.SelectedFontSize.Should().BeApproximately(12.0 * 4 / 3, 1e-9, + "the stored value stays device-independent pixels for 9.x round-tripping"); + page.SelectedFontSizePoints.Should().Be("12"); + } + finally + { + settings.SelectedFontSize = original; + } + } + + [AvaloniaTest] + public void External_Pixel_Change_Refreshes_The_Points_Text() + { + // Reset-to-defaults and LoadFromXml write SelectedFontSize directly; the dialog text + // must follow via PropertyChanged on SelectedFontSizePoints. + var (page, settings) = CreatePage(); + var original = settings.SelectedFontSize; + try + { + var notified = new List(); + page.PropertyChanged += (_, e) => notified.Add(e.PropertyName); + + settings.SelectedFontSize = 20; + + notified.Should().Contain(nameof(DisplaySettingsViewModel.SelectedFontSizePoints)); + page.SelectedFontSizePoints.Should().Be("15"); + } + finally + { + settings.SelectedFontSize = original; + } + } + + [AvaloniaTest] + public void Setting_Points_Through_The_Dialog_Does_Not_Echo_A_Text_Notification() + { + // While the user is typing in the size box, the setter must not raise PropertyChanged + // for SelectedFontSizePoints - the binding would rewrite the box mid-keystroke + // (typing "10." would snap back to "10" before the fraction can be completed). + var (page, settings) = CreatePage(); + var original = settings.SelectedFontSize; + try + { + var notified = new List(); + page.PropertyChanged += (_, e) => notified.Add(e.PropertyName); + + page.SelectedFontSizePoints = "14"; + + notified.Should().NotContain(nameof(DisplaySettingsViewModel.SelectedFontSizePoints)); + } + finally + { + settings.SelectedFontSize = original; + } + } + + [AvaloniaTest] + public void NonNumeric_Input_Is_Ignored() + { + var (page, settings) = CreatePage(); + var original = settings.SelectedFontSize; + try + { + settings.SelectedFontSize = 16; + page.SelectedFontSizePoints = "abc"; + settings.SelectedFontSize.Should().Be(16, + "transient garbage while typing must not move the stored size"); + } + finally + { + settings.SelectedFontSize = original; + } + } + + [AvaloniaTest] + public void Typed_Sizes_Are_Clamped_To_The_6_To_72_Point_Range() + { + var (page, settings) = CreatePage(); + var original = settings.SelectedFontSize; + try + { + page.SelectedFontSizePoints = "1"; + settings.SelectedFontSize.Should().BeApproximately(6.0 * 4 / 3, 1e-9); + + page.SelectedFontSizePoints = "500"; + settings.SelectedFontSize.Should().BeApproximately(72.0 * 4 / 3, 1e-9); + } + finally + { + settings.SelectedFontSize = original; + } + } + + [AvaloniaTest] + public void Size_List_Offers_6_Through_24_Points_Like_The_WPF_Host() + { + var (page, _) = CreatePage(); + page.FontSizes.Should().Equal(Enumerable.Range(6, 24 - 6 + 1)); + } + + [AvaloniaTest] + public async Task Display_Panel_Size_Box_Is_An_Editable_ComboBox_Showing_Points() + { + var window = AppComposition.Current.GetExport(); + window.Show(); + var settings = AppComposition.Current.GetExport(); + var original = settings.DisplaySettings.SelectedFontSize; + try + { + settings.DisplaySettings.SelectedFontSize = 10.0 * 4 / 3; + + AppComposition.Current.GetExport() + .GetCommand(nameof(Resources._Options)).Execute(null); + var vm = (MainWindowViewModel)window.DataContext!; + var model = (OptionsPageModel)vm.DockWorkspace.Documents!.VisibleDockables! + .OfType().First(t => t.Content is OptionsPageModel).Content!; + model.SelectedPage = model.Pages.OfType().Single(); + TestCapture.Step("display-page-selected"); + + var panel = await window.WaitForComponent(); + var box = panel.FindControl("fontSizeComboBox"); + ((object?)box).Should().NotBeNull("the size box must be the named editable ComboBox"); + Dispatcher.UIThread.RunJobs(); + + box!.IsEditable.Should().BeTrue("custom sizes must be typeable, like Notepad's font page"); + box.Text.Should().Be("10", "the box shows points, not device-independent pixels"); + } + finally + { + settings.DisplaySettings.SelectedFontSize = original; + } + } +} diff --git a/ILSpy/Options/DisplaySettingsPanel.axaml b/ILSpy/Options/DisplaySettingsPanel.axaml index b923c4dde..2b18e2b6c 100644 --- a/ILSpy/Options/DisplaySettingsPanel.axaml +++ b/ILSpy/Options/DisplaySettingsPanel.axaml @@ -20,16 +20,21 @@ - + - - + + +