R1: Umbau auf Multi-Projekt-Struktur (Vorbild PolytraderSharp)

Kurskorrektur: Vorbild ist PolytraderSharp (C#), nicht die veraltete Python-Version.
Reiner Strukturumbau, Verhalten unveraendert.

- src/IBKRTrader.Core (classlib): Core-Code + UI-Contract (ModuleFormBase, WindowManager)
- src/IBKRTrader.Modules.CongressTrading (classlib, referenziert nur Core)
- Root: IBKRTrader.App (WinExe) referenziert Core + Modul
- tests/IBKRTrader.Tests: Referenzen auf Core + Modul
- Neue .slnx; ungenutztes HtmlAgilityPack entfernt
- docs/ARCHITECTURE.md: korrigierter Ziel-Stand + R-Phasenplan
- Build + 38/38 Tests gruen; App startet

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
This commit is contained in:
Richard
2026-07-27 20:26:25 +02:00
parent 2ad4b55db1
commit c20538c8d8
61 changed files with 171 additions and 129 deletions
@@ -0,0 +1,100 @@
using FluentAssertions;
using IBKRTrader.UI;
namespace IBKRTrader.Tests.UI;
[Trait("cat", "unit")]
public class WindowManagerTests
{
/// <summary>
/// Testbare Variante: unterdrückt echte Show-/Focus-Aufrufe (kein Message-Loop),
/// zählt aber die Focus-Aufrufe.
/// </summary>
private sealed class TestWindowManager : WindowManager
{
public int FocusCount;
protected override void Present(Form form) { /* kein Show im Test */ }
protected override void Focus(Form form) => FocusCount++;
}
[Fact]
public void OpenOrFocus_NewKey_InvokesFactory_AndTracks()
{
var wm = new TestWindowManager();
var created = 0;
using var form = wm.OpenOrFocus("CT", () => { created++; return new Form(); });
created.Should().Be(1);
wm.IsOpen("CT").Should().BeTrue();
wm.OpenCount.Should().Be(1);
}
[Fact]
public void OpenOrFocus_SameKey_FocusesExisting_DoesNotRecreate()
{
var wm = new TestWindowManager();
var created = 0;
using var first = wm.OpenOrFocus("CT", () => { created++; return new Form(); });
var second = wm.OpenOrFocus("CT", () => { created++; return new Form(); });
created.Should().Be(1);
wm.FocusCount.Should().Be(1);
second.Should().BeSameAs(first);
wm.OpenCount.Should().Be(1);
}
[Fact]
public void OpenOrFocus_DifferentKeys_TracksSeparately()
{
var wm = new TestWindowManager();
using var a = wm.OpenOrFocus("A", () => new Form());
using var b = wm.OpenOrFocus("B", () => new Form());
wm.OpenCount.Should().Be(2);
wm.IsOpen("A").Should().BeTrue();
wm.IsOpen("B").Should().BeTrue();
}
[Fact]
public void OpenOrFocus_AfterDisposed_RecreatesWindow()
{
var wm = new TestWindowManager();
var created = 0;
var first = wm.OpenOrFocus("CT", () => { created++; return new Form(); });
first.Dispose();
wm.IsOpen("CT").Should().BeFalse();
using var second = wm.OpenOrFocus("CT", () => { created++; return new Form(); });
created.Should().Be(2);
second.Should().NotBeSameAs(first);
}
[Fact]
public void CloseAll_RemovesAllWindows()
{
var wm = new TestWindowManager();
wm.OpenOrFocus("A", () => new Form());
wm.OpenOrFocus("B", () => new Form());
wm.CloseAll();
wm.OpenCount.Should().Be(0);
wm.IsOpen("A").Should().BeFalse();
}
[Fact]
public void OpenOrFocus_BlankKey_Throws()
{
var wm = new TestWindowManager();
var act = () => wm.OpenOrFocus(" ", () => new Form());
act.Should().Throw<ArgumentException>();
}
}