diff --git a/ILSpy.Tests/MainWindow/MainToolBarLayoutTests.cs b/ILSpy.Tests/MainWindow/MainToolBarLayoutTests.cs index 2b7021b31..70733c195 100644 --- a/ILSpy.Tests/MainWindow/MainToolBarLayoutTests.cs +++ b/ILSpy.Tests/MainWindow/MainToolBarLayoutTests.cs @@ -30,6 +30,7 @@ using ICSharpCode.ILSpy.Properties; using ILSpy; using ILSpy.AppEnv; +using ILSpy.Commands; using ILSpy.Search; using ILSpy.ViewModels; using ILSpy.Views; @@ -115,6 +116,31 @@ public class MainToolBarLayoutTests "clicking the Show-Search toolbar button must activate the search tool pane"); } + [AvaloniaTest] + public void Every_ExportToolbarCommand_Resolves_An_Icon() + { + // `MainToolBar.BuildButton` falls back to a text label when `ResolveIcon` can't find + // the `Images/` asset — that's a UX regression (and a silent one, since the + // button still works). Pin every MEF-exported toolbar command to a real IImage so the + // next "we forgot to import the SVG" lands as a test failure, not a user report. + var registry = AppComposition.Current.GetExport(); + foreach (var entry in registry.Commands) + { + var iconPath = entry.Metadata.ToolbarIcon; + iconPath.Should().NotBeNullOrEmpty( + "every toolbar command must declare a ToolbarIcon"); + var name = iconPath!.StartsWith("Images/", System.StringComparison.Ordinal) + ? iconPath["Images/".Length..] + : iconPath; + var field = typeof(global::ILSpy.Images.Images) + .GetField(name, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); + field.Should().NotBeNull( + $"toolbar command with ToolTip='{entry.Metadata.ToolTip}' references Images/{name} but no static field with that name exists in ILSpy.Images.Images"); + field!.GetValue(null).Should().NotBeNull( + $"the Images.{name} field is declared but null at runtime"); + } + } + [AvaloniaTest] public async Task MainToolBar_Button_Order_Mirrors_WPF() { diff --git a/ILSpy/Assets/Icons/CollapseAll.svg b/ILSpy/Assets/Icons/CollapseAll.svg new file mode 100644 index 000000000..a7b706705 --- /dev/null +++ b/ILSpy/Assets/Icons/CollapseAll.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ILSpy/Assets/Icons/Refresh.svg b/ILSpy/Assets/Icons/Refresh.svg new file mode 100644 index 000000000..a6f09ee5f --- /dev/null +++ b/ILSpy/Assets/Icons/Refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ILSpy/Assets/Icons/Sort.svg b/ILSpy/Assets/Icons/Sort.svg new file mode 100644 index 000000000..75830ceb6 --- /dev/null +++ b/ILSpy/Assets/Icons/Sort.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ILSpy/Images.cs b/ILSpy/Images.cs index 6326218a4..aca92c894 100644 --- a/ILSpy/Images.cs +++ b/ILSpy/Images.cs @@ -76,6 +76,9 @@ namespace ILSpy.Images public static readonly IImage Forward = LoadSvg(nameof(Forward)); public static readonly IImage Open = LoadSvg(nameof(Open)); public static readonly IImage Save = LoadSvg(nameof(Save)); + public static readonly IImage Refresh = LoadSvg(nameof(Refresh)); + public static readonly IImage Sort = LoadSvg(nameof(Sort)); + public static readonly IImage CollapseAll = LoadSvg(nameof(CollapseAll)); public static readonly IImage ShowPublicOnly = LoadSvg(nameof(ShowPublicOnly)); public static readonly IImage ShowPrivateInternal = LoadSvg(nameof(ShowPrivateInternal)); public static readonly IImage ShowAll = LoadSvg(nameof(ShowAll));