P1a: PolyTrader.Core auf net8.0 - UI-Contract toolkit-neutral

Der Core traegt keine WinForms-/System.Drawing-Abhaengigkeit mehr und baut als
plattformneutrales net8.0 (verifiziert: publish -r linux-x64 erfolgreich).

- ModuleView.CreateForm (Func<Form>) -> CreateView (Func<object>): die Shell kennt
  ihr Toolkit und castet, der Core nicht. Avalonia kann denselben Contract nutzen.
- ModuleView.Icon (System.Drawing.Image, seit .NET 7 Windows-only) -> IconKey (string).
  Aufloesung Schluessel->Bildressource neu in Ui/ViewIcons.cs, ersetzt Program.AssignMenuIcons.
- WindowMenu.cs (reine WinForms-Logik) aus dem Core nach Ui/ verschoben.
- WindowMenuTests entfernt: testet die eingefrorene WinForms-Menuelogik, die das
  Testprojekt nach dem Core-Schnitt nicht mehr erreicht. Im Tag winforms-final erhalten;
  das Avalonia-Gegenstueck bekommt eigene Tests (P6/P9).

Module und App bleiben vorerst net8.0-windows - ihre UI zieht erst mit der
Avalonia-Portierung um. Windows-App unveraendert lauffaehig (--smoke-ui gruen).

442 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-06 17:10:41 +02:00
co-authored by Claude Opus 5
parent fd6d62618f
commit ad1593bfc1
12 changed files with 121 additions and 189 deletions
-133
View File
@@ -1,133 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using PolyTrader.Core.Modularity;
using Xunit;
namespace PolyTrader.Tests
{
/// <summary>Sicherheitsnetz für das gemeinsame „Fenster"-Menü (Slice 1): Inhalt + Verhalten.</summary>
public class WindowMenuTests
{
private sealed class FakeUiHost : IModuleUiHost
{
public List<ModuleView> ViewList { get; } = new();
public HashSet<string> OpenIds { get; } = new();
public List<string> Opened { get; } = new();
public bool MainActivated { get; private set; }
public bool ShutdownRequested { get; private set; }
public IReadOnlyList<ModuleView> Views => ViewList;
public bool IsOpen(string viewId) => OpenIds.Contains(viewId);
public void OpenView(string viewId) => Opened.Add(viewId);
public void ActivateMain() => MainActivated = true;
public void RequestShutdown() => ShutdownRequested = true;
public void RegisterView(ModuleView view) => ViewList.Add(view);
public event Action? OpenStateChanged { add { } remove { } }
}
private static FakeUiHost Host()
{
var h = new FakeUiHost();
h.RegisterView(new ModuleView { Id = "core.dashboard", Title = "Dashboard", Order = 1 });
h.RegisterView(new ModuleView { Id = "core.terminal", Title = "Terminal / Logs", Order = 2 });
h.OpenIds.Add("core.terminal"); // Terminal ist offen
return h;
}
[Fact]
public void Populate_lists_launcher_views_and_exit_side_by_side()
{
var host = Host();
var menu = new MenuStrip();
// currentViewId == null => Launcher (Hauptprozess): rechte Aktion ist „Beenden".
WindowMenu.Populate(menu, host, currentViewId: null);
var texts = menu.Items.OfType<ToolStripMenuItem>().Select(i => i.Text).ToList();
Assert.Equal("Launcher", texts[0]); // Launcher als erster Top-Level-Eintrag
Assert.Contains("Dashboard", texts);
Assert.Contains("Terminal / Logs", texts);
Assert.Contains("Beenden", texts);
}
[Fact]
public void Non_launcher_window_offers_close_window_not_exit()
{
var host = Host();
var menu = new MenuStrip();
// currentViewId != null => Modul-/Core-Fenster: KEIN „Beenden", nur „Fenster schließen".
WindowMenu.Populate(menu, host, currentViewId: "core.dashboard");
var texts = menu.Items.OfType<ToolStripMenuItem>().Select(i => i.Text).ToList();
Assert.Contains("Fenster schließen", texts);
Assert.DoesNotContain("Beenden", texts);
// Klick auf „Fenster schließen" fordert NICHT das App-Shutdown an.
menu.Items.OfType<ToolStripMenuItem>().First(i => i.Text == "Fenster schließen").PerformClick();
Assert.False(host.ShutdownRequested);
}
[Fact]
public void Views_carry_their_icon_in_the_menu()
{
var host = new FakeUiHost();
using var icon = new System.Drawing.Bitmap(16, 16);
host.RegisterView(new ModuleView { Id = "core.dashboard", Title = "Dashboard", Order = 1, Icon = icon });
var menu = new MenuStrip();
WindowMenu.Populate(menu, host, currentViewId: null);
var dashboard = menu.Items.OfType<ToolStripMenuItem>().First(i => i.Text == "Dashboard");
Assert.Same(icon, dashboard.Image);
Assert.Equal(ToolStripItemDisplayStyle.ImageAndText, dashboard.DisplayStyle);
}
[Fact]
public void Current_view_is_bold_and_checked_open_view_is_checked()
{
var host = Host();
var menu = new MenuStrip();
WindowMenu.Populate(menu, host, currentViewId: "core.dashboard");
var dashboard = menu.Items.OfType<ToolStripMenuItem>().First(i => i.Text == "Dashboard");
var terminal = menu.Items.OfType<ToolStripMenuItem>().First(i => i.Text == "Terminal / Logs");
Assert.True(dashboard.Checked); // aktuelles Fenster
Assert.True(dashboard.Font.Bold);
Assert.True(terminal.Checked); // offenes Fenster
Assert.False(terminal.Font.Bold);
}
[Fact]
public void Clicking_items_navigates()
{
var host = Host();
var menu = new MenuStrip();
WindowMenu.Populate(menu, host, currentViewId: null);
var items = menu.Items.OfType<ToolStripMenuItem>().ToList();
items.First(i => i.Text == "Launcher").PerformClick();
items.First(i => i.Text == "Terminal / Logs").PerformClick();
Assert.True(host.MainActivated);
Assert.Contains("core.terminal", host.Opened);
}
[Fact]
public void Clicking_exit_requests_shutdown_not_immediate_exit()
{
var host = Host();
var menu = new MenuStrip();
WindowMenu.Populate(menu, host, currentViewId: null);
menu.Items.OfType<ToolStripMenuItem>().First(i => i.Text == "Beenden").PerformClick();
Assert.True(host.ShutdownRequested);
}
}
}