.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
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.
 
 
 

161 lines
6.2 KiB

// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// 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.Composition;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests;
[TestFixture]
public class ContextMenuFrameworkTests
{
[AvaloniaTest]
public void Build_Includes_Visible_Entries_With_Header_And_Skips_Hidden_Ones()
{
// ContextMenuProvider.Build is the pure-function core: it takes (entries, context) and
// returns a ContextMenu populated with one MenuItem per entry whose IsVisible(context)
// returns true. Entries whose IsVisible returns false are skipped entirely.
// Arrange — three entries, one hidden.
var entries = new IContextMenuEntryExport[] {
Stub("Visible one", category: null, order: 0, visible: true, enabled: true),
Stub("Hidden", category: null, order: 1, visible: false, enabled: true),
Stub("Visible two", category: null, order: 2, visible: true, enabled: true),
};
var context = new TextViewContext();
// Act — build the menu.
var menu = ContextMenuProvider.Build(entries, context);
// Assert — only the two visible entries land, in declared order.
menu.Should().NotBeNull();
var items = menu!.Items.OfType<MenuItem>().ToList();
items.Should().HaveCount(2);
items[0].Header.Should().Be("Visible one");
items[1].Header.Should().Be("Visible two");
}
[AvaloniaTest]
public void Build_Marks_Entry_Disabled_When_Is_Enabled_Returns_False()
{
// Arrange — entry that's visible but disabled.
var entries = new IContextMenuEntryExport[] {
Stub("Disabled entry", category: null, order: 0, visible: true, enabled: false),
};
var context = new TextViewContext();
// Act
var menu = ContextMenuProvider.Build(entries, context)!;
// Assert — item is present but its IsEnabled flag is false; clicking it does nothing.
var item = menu.Items.OfType<MenuItem>().Single();
item.IsEnabled.Should().BeFalse();
}
[AvaloniaTest]
public void Build_Returns_Null_When_No_Visible_Entries_So_The_Menu_Is_Suppressed()
{
// An empty menu shouldn't be shown at all — the call site decides whether to suppress
// the right-click affordance based on a null return.
// Arrange — single entry, hidden.
var entries = new IContextMenuEntryExport[] {
Stub("Hidden", category: null, order: 0, visible: false, enabled: true),
};
// Act + Assert
ContextMenuProvider.Build(entries, new TextViewContext()).Should().BeNull();
}
[AvaloniaTest]
public void Build_Inserts_Separators_Between_Categories()
{
// Arrange — two entries in category A, one in category B.
var entries = new IContextMenuEntryExport[] {
Stub("A1", category: "A", order: 0, visible: true, enabled: true),
Stub("A2", category: "A", order: 1, visible: true, enabled: true),
Stub("B1", category: "B", order: 2, visible: true, enabled: true),
};
// Act
var menu = ContextMenuProvider.Build(entries, new TextViewContext())!;
// Assert — order is A1, A2, separator, B1.
var children = menu.Items.ToList();
children.Should().HaveCount(4);
children[0].Should().BeOfType<MenuItem>().Which.Header.Should().Be("A1");
children[1].Should().BeOfType<MenuItem>().Which.Header.Should().Be("A2");
children[2].Should().BeOfType<Separator>();
children[3].Should().BeOfType<MenuItem>().Which.Header.Should().Be("B1");
}
[AvaloniaTest]
public void Click_On_Menu_Item_Invokes_The_Entry_Execute_With_The_Context()
{
// Arrange — record the context the entry receives at Execute time.
TextViewContext? received = null;
var entry = new RecordingEntry(c => received = c);
var export = new StubExport(
entry,
new ContextMenuEntryMetadata { Header = "Run me", Order = 0 });
var context = new TextViewContext();
// Act — build the menu, click the item.
var menu = ContextMenuProvider.Build(new[] { export }, context)!;
var item = menu.Items.OfType<MenuItem>().Single();
item.RaiseEvent(new global::Avalonia.Interactivity.RoutedEventArgs(MenuItem.ClickEvent));
// Assert — Execute fired with the supplied context.
ReferenceEquals(received, context).Should().BeTrue();
}
static IContextMenuEntryExport Stub(string header, string? category, double order, bool visible, bool enabled)
=> new StubExport(
new StubEntry(visible, enabled),
new ContextMenuEntryMetadata { Header = header, Category = category, Order = order });
sealed class StubEntry(bool visible, bool enabled) : IContextMenuEntry
{
public bool IsVisible(TextViewContext context) => visible;
public bool IsEnabled(TextViewContext context) => enabled;
public void Execute(TextViewContext context) { }
}
sealed class RecordingEntry(System.Action<TextViewContext> onExecute) : IContextMenuEntry
{
public bool IsVisible(TextViewContext context) => true;
public bool IsEnabled(TextViewContext context) => true;
public void Execute(TextViewContext context) => onExecute(context);
}
sealed class StubExport(IContextMenuEntry entry, ContextMenuEntryMetadata metadata) : IContextMenuEntryExport
{
public IContextMenuEntry Value { get; } = entry;
public ContextMenuEntryMetadata Metadata { get; } = metadata;
}
}