mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
6.2 KiB
179 lines
6.2 KiB
// 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.Linq; |
|
|
|
using Avalonia.Headless.NUnit; |
|
|
|
using AwesomeAssertions; |
|
|
|
using ICSharpCode.Decompiler.Metadata; |
|
using ICSharpCode.Decompiler.TypeSystem; |
|
using ICSharpCode.ILSpy.Languages; |
|
using ICSharpCode.ILSpy.Search; |
|
|
|
using NUnit.Framework; |
|
|
|
namespace ICSharpCode.ILSpy.Tests.Search; |
|
|
|
// Search results must carry the same composed icons as the assembly tree: base symbol by |
|
// entity kind, plus accessibility and static overlays. The factory therefore has to go |
|
// through Images.GetIcon (via the tree nodes' GetIcon helpers), not hand out bare base |
|
// images — a bare Images.Field for a private static field loses both mini-overlays. |
|
[TestFixture] |
|
public class SearchResultFactoryIconTests |
|
{ |
|
AvaloniaSearchResultFactory factory = null!; |
|
ITypeDefinition fixtureType = null!; |
|
|
|
[OneTimeSetUp] |
|
public void LoadFixtureTypeFromOwnAssembly() |
|
{ |
|
// Read the fixture entities from this test assembly's own metadata, so the |
|
// accessibility/static flags come from a real type system, not mocks. |
|
var path = typeof(SearchResultFactoryIconTests).Assembly.Location; |
|
var file = new PEFile(path); |
|
var resolver = new UniversalAssemblyResolver(path, throwOnError: false, |
|
targetFramework: file.DetectTargetFrameworkId()); |
|
var typeSystem = new DecompilerTypeSystem(file, resolver); |
|
fixtureType = typeSystem.MainModule.TypeDefinitions |
|
.Single(t => t.Name == nameof(SearchIconFixture)); |
|
factory = new AvaloniaSearchResultFactory(new CSharpLanguage()); |
|
} |
|
|
|
[AvaloniaTest] |
|
public void Private_Static_Field_Result_Composes_Static_And_Private_Overlays() |
|
{ |
|
var field = fixtureType.Fields.Single(f => f.Name == "privateStaticField"); |
|
|
|
var result = factory.Create(field); |
|
|
|
var icon = result.Image.Should().BeOfType<LayeredImage>().Subject; |
|
icon.BaseImage.Should().BeSameAs(Images.Field); |
|
icon.BaseScale.Should().Be(0.8); |
|
icon.Overlays.Should().Equal(Images.OverlayStatic, Images.OverlayPrivate); |
|
} |
|
|
|
[AvaloniaTest] |
|
public void Nested_Interface_Result_Uses_Interface_Base_Icon() |
|
{ |
|
var nested = fixtureType.NestedTypes |
|
.Single(t => t.Name == nameof(SearchIconFixture.INested)).GetDefinition()!; |
|
|
|
var icon = (LayeredImage)factory.Create(nested).Image; |
|
|
|
icon.BaseImage.Should().BeSameAs(Images.Interface); |
|
} |
|
|
|
[AvaloniaTest] |
|
public void Nested_Enum_Result_Uses_Enum_Base_Icon() |
|
{ |
|
var nested = fixtureType.NestedTypes |
|
.Single(t => t.Name == "NestedEnum").GetDefinition()!; |
|
|
|
var icon = (LayeredImage)factory.Create(nested).Image; |
|
|
|
icon.BaseImage.Should().BeSameAs(Images.Enum); |
|
} |
|
|
|
[AvaloniaTest] |
|
public void Protected_Internal_Nested_Type_Gets_Plain_Protected_Overlay() |
|
{ |
|
var nested = fixtureType.NestedTypes |
|
.Single(t => t.Name == "NestedProtectedInternal").GetDefinition()!; |
|
|
|
var icon = (LayeredImage)factory.Create(nested).Image; |
|
|
|
// Types map protected-internal to the plain protected badge; only members show |
|
// the combined protected-internal badge. Matches the WPF frontend's type-only |
|
// overlay mapping. |
|
icon.Overlays.Should().Equal(Images.OverlayProtected); |
|
} |
|
|
|
[AvaloniaTest] |
|
public void Location_Image_Reflects_Declaring_Type_Icon() |
|
{ |
|
var field = fixtureType.Fields.Single(f => f.Name == "privateStaticField"); |
|
|
|
var result = factory.Create(field); |
|
|
|
// The declaring type (SearchIconFixture) is a top-level internal class. |
|
var location = result.LocationImage.Should().BeOfType<LayeredImage>().Subject; |
|
location.BaseImage.Should().BeSameAs(Images.Class); |
|
location.Overlays.Should().Equal(Images.OverlayInternal); |
|
} |
|
|
|
[AvaloniaTest] |
|
public void Internal_Static_Method_Result_Composes_Static_And_Internal_Overlays() |
|
{ |
|
var method = fixtureType.Methods.Single(m => m.Name == "InternalStaticMethod"); |
|
|
|
var icon = (LayeredImage)factory.Create(method).Image; |
|
|
|
icon.BaseImage.Should().BeSameAs(Images.Method); |
|
icon.Overlays.Should().Equal(Images.OverlayStatic, Images.OverlayInternal); |
|
} |
|
|
|
[AvaloniaTest] |
|
public void Protected_Property_Result_Composes_Protected_Overlay() |
|
{ |
|
var property = fixtureType.Properties.Single(p => p.Name == "ProtectedProperty"); |
|
|
|
var icon = (LayeredImage)factory.Create(property).Image; |
|
|
|
icon.BaseImage.Should().BeSameAs(Images.Property); |
|
icon.Overlays.Should().Equal(Images.OverlayProtected); |
|
} |
|
|
|
[AvaloniaTest] |
|
public void Private_Event_Result_Composes_Private_Overlay() |
|
{ |
|
var @event = fixtureType.Events.Single(e => e.Name == "privateEvent"); |
|
|
|
var icon = (LayeredImage)factory.Create(@event).Image; |
|
|
|
icon.BaseImage.Should().BeSameAs(Images.Event); |
|
icon.Overlays.Should().Equal(Images.OverlayPrivate); |
|
} |
|
|
|
[AvaloniaTest] |
|
public void Top_Level_Type_Result_Falls_Back_To_Namespace_Location_Image() |
|
{ |
|
// A top-level type has no declaring type, so the location column shows its namespace. |
|
factory.Create(fixtureType).LocationImage.Should().BeSameAs(Images.Namespace); |
|
} |
|
} |
|
|
|
#pragma warning disable CS0169, CS0067 // members exist only as metadata probes for the tests above |
|
class SearchIconFixture |
|
{ |
|
static int privateStaticField; |
|
|
|
internal interface INested { } |
|
|
|
enum NestedEnum { None } |
|
|
|
protected internal class NestedProtectedInternal { } |
|
|
|
internal static void InternalStaticMethod() { } |
|
|
|
protected int ProtectedProperty { get; } |
|
|
|
event System.EventHandler? privateEvent; |
|
} |
|
#pragma warning restore CS0169, CS0067
|
|
|