Skip to content

Commit d616fcf

Browse files
author
BRUNER Patrick
committed
small updates
1 parent 0295266 commit d616fcf

4 files changed

Lines changed: 187 additions & 13 deletions

File tree

src/LogExpert.Core/Interface/ILogExpertProxy.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ public interface ILogExpertProxy
2929
/// <param name="logWin"></param>
3030
void WindowClosed(ILogTabWindow logWin);
3131

32+
/// <summary>
33+
/// Notifies the proxy that a window has been activated by the user.
34+
/// Used to track which window should receive new files when "Allow Only One Instance" is enabled.
35+
/// </summary>
36+
/// <param name="window">The window that was activated</param>
37+
void NotifyWindowActivated(ILogTabWindow window);
38+
3239
int GetLogWindowCount();
3340

3441
#endregion

src/LogExpert.Tests/IPC/LockInstancePriorityTests.cs

Lines changed: 163 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
namespace LogExpert.Tests.IPC;
1010

1111
/// <summary>
12-
/// Unit tests for Lock Instance Priority feature
12+
/// Unit tests for Lock Instance Priority feature and Phase 2 Active Window Tracking
1313
/// Tests that lock instance behavior works correctly with "Allow Only One Instance"
14+
/// and that the most recently activated window receives new files
1415
/// </summary>
1516
[TestFixture]
1617
public class LockInstancePriorityTests
@@ -37,8 +38,118 @@ public void TearDown ()
3738
AbstractLogTabWindow.StaticData.CurrentLockedMainWindow = null;
3839
}
3940

41+
#region Phase 2: Active Window Tracking Tests
42+
43+
[Test]
44+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Test")]
45+
public void NotifyWindowActivated_UpdatesMostRecentActiveWindow ()
46+
{
47+
// Arrange
48+
var proxy = new LogExpertProxy(_mockWindow1.Object);
49+
50+
// Act
51+
proxy.NotifyWindowActivated(_mockWindow2.Object);
52+
53+
// Assert - verify by calling LoadFiles and checking which window is used
54+
// Since we can't directly access _mostRecentActiveWindow (private field),
55+
// we verify behavior through LoadFiles
56+
57+
// This test documents that NotifyWindowActivated is called and tracked
58+
Assert.Pass("NotifyWindowActivated successfully updates internal tracking");
59+
}
60+
61+
[Test]
62+
public void LoadFiles_WithNoActiveWindow_UsesLastWindowInList ()
63+
{
64+
// Arrange
65+
var proxy = new LogExpertProxy(_mockWindow1.Object);
66+
var files = new[] { "test.log" };
67+
68+
// Setup mock to track calls
69+
_ = _mockWindow1.Setup(w => w.Invoke(It.IsAny<Delegate>(), It.IsAny<object[]>()))
70+
.Returns((Delegate d, object[] args) => null);
71+
_ = _mockWindow1.Setup(w => w.LoadFiles(It.IsAny<string[]>()));
72+
73+
// Act
74+
proxy.LoadFiles(files);
75+
76+
// Assert - LoadFiles should use the last (and only) window in the list
77+
_mockWindow1.Verify(w => w.LoadFiles(files), Times.Once);
78+
}
79+
80+
[Test]
81+
public void LoadFiles_AfterWindowActivated_UsesMostRecentActiveWindow ()
82+
{
83+
// Arrange
84+
var proxy = new LogExpertProxy(_mockWindow1.Object);
85+
86+
// Setup mocks
87+
_ = _mockWindow1.Setup(w => w.Invoke(It.IsAny<Delegate>(), It.IsAny<object[]>()))
88+
.Returns((Delegate d, object[] args) => null);
89+
_ = _mockWindow2.Setup(w => w.Invoke(It.IsAny<Delegate>(), It.IsAny<object[]>()))
90+
.Returns((Delegate d, object[] args) => null);
91+
92+
_ = _mockWindow1.Setup(w => w.LoadFiles(It.IsAny<string[]>()));
93+
_ = _mockWindow2.Setup(w => w.LoadFiles(It.IsAny<string[]>()));
94+
95+
// Simulate window activation
96+
proxy.NotifyWindowActivated(_mockWindow2.Object);
97+
98+
var files = new[] { "test.log" };
99+
100+
// Act
101+
proxy.LoadFiles(files);
102+
103+
// Assert - should use window2 since it was most recently activated
104+
_mockWindow2.Verify(w => w.LoadFiles(files), Times.Once);
105+
_mockWindow1.Verify(w => w.LoadFiles(It.IsAny<string[]>()), Times.Never);
106+
}
107+
108+
[Test]
109+
public void LoadFiles_MultipleActivations_UsesLastActivatedWindow ()
110+
{
111+
// Arrange
112+
var proxy = new LogExpertProxy(_mockWindow1.Object);
113+
114+
// Setup mocks
115+
_ = _mockWindow1.Setup(w => w.Invoke(It.IsAny<Delegate>(), It.IsAny<object[]>()))
116+
.Returns((Delegate d, object[] args) => null);
117+
_ = _mockWindow2.Setup(w => w.Invoke(It.IsAny<Delegate>(), It.IsAny<object[]>()))
118+
.Returns((Delegate d, object[] args) => null);
119+
120+
_ = _mockWindow1.Setup(w => w.LoadFiles(It.IsAny<string[]>()));
121+
_ = _mockWindow2.Setup(w => w.LoadFiles(It.IsAny<string[]>()));
122+
123+
// Act - Simulate multiple activations
124+
proxy.NotifyWindowActivated(_mockWindow1.Object);
125+
proxy.NotifyWindowActivated(_mockWindow2.Object);
126+
proxy.NotifyWindowActivated(_mockWindow1.Object); // Window1 is most recent
127+
128+
var files = new[] { "test.log" };
129+
proxy.LoadFiles(files);
130+
131+
// Assert - should use window1 since it was activated last
132+
_mockWindow1.Verify(w => w.LoadFiles(files), Times.Once);
133+
_mockWindow2.Verify(w => w.LoadFiles(It.IsAny<string[]>()), Times.Never);
134+
}
135+
136+
[Test]
137+
public void NotifyWindowActivated_WithNullWindow_DoesNotCrash ()
138+
{
139+
// Arrange
140+
var proxy = new LogExpertProxy(_mockWindow1.Object);
141+
142+
// Act & Assert - should not throw
143+
Assert.DoesNotThrow(() => proxy.NotifyWindowActivated(null));
144+
}
145+
146+
#endregion
147+
148+
#region Manual Tests: Lock Instance Priority Tests
149+
40150
[Test]
41151
[Ignore("Requires UI thread context - manual testing recommended")]
152+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Test")]
42153
public void NewWindowOrLockedWindow_WithLockedWindow_LoadsInLockedWindow ()
43154
{
44155
// This test requires a proper UI context and cannot be run in unit test environment
@@ -52,6 +163,8 @@ public void NewWindowOrLockedWindow_WithLockedWindow_LoadsInLockedWindow ()
52163
}
53164

54165
[Test]
166+
[Ignore("Requires UI thread context - manual testing recommended")]
167+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Test")]
55168
public void NewWindowOrLockedWindow_WithoutLockedWindow_ShouldUseLoadFiles ()
56169
{
57170
// This test documents the expected behavior
@@ -60,48 +173,54 @@ public void NewWindowOrLockedWindow_WithoutLockedWindow_ShouldUseLoadFiles ()
60173
// Expected behavior:
61174
// 1. Check all windows for locked window
62175
// 2. If no locked window found, call LoadFiles() instead of NewWindow()
63-
// 3. LoadFiles() loads in most recent window (last in window list)
176+
// 3. LoadFiles() loads in most recent active window (Phase 2) or last created window (Phase 1)
64177

65178
Assert.Pass("Expected behavior documented - integration test required");
66179
}
67180

68181
[Test]
69-
public void LoadFiles_UsesLastWindowInList ()
182+
[Ignore("Requires UI thread context - manual testing recommended")]
183+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Test")]
184+
public void LoadFiles_Phase2_UsesActiveWindowTracking ()
70185
{
71-
// This test documents that LoadFiles should use the last window in the list
72-
// In Phase 2, this will be enhanced to use the most recently activated window
186+
// This test documents that LoadFiles uses active window tracking in Phase 2
73187

74-
// Expected behavior for Phase 1:
75-
// - LoadFiles() gets last window from _windowList
188+
// Expected behavior for Phase 2:
189+
// - LoadFiles() gets most recently activated window from _mostRecentActiveWindow
190+
// - If _mostRecentActiveWindow is null, falls back to last window in _windowList
76191
// - Sets that window to foreground
77192
// - Loads files in that window
78193

79-
Assert.Pass("Phase 1 behavior documented - uses last window in list");
194+
Assert.Pass("behavior documented - uses active window tracking");
80195
}
81196

197+
#endregion
198+
82199
#region Documentation Tests
83200

84201
[Test]
202+
[Ignore("Documentation")]
203+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Test")]
85204
public void Priority_LockedWindowTakesPrecedenceOverAllowOnlyOne ()
86205
{
87-
// Documents stakeholder decision:
88206
// When both "Lock Instance" and "Allow Only One Instance" are active,
89207
// the locked window takes priority
90208

91209
// Priority order:
92210
// 1. If locked window exists -> use it (highest priority)
93-
// 2. Else if AllowOnlyOneInstance -> load in most recent window
211+
// 2. Else if AllowOnlyOneInstance -> load in most recent active window (Phase 2)
94212
// 3. Else -> create new window
95213

96214
Assert.Pass("Priority order documented");
97215
}
98216

99217
[Test]
218+
[Ignore("Documentation")]
219+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Test")]
100220
public void AllowOnlyOneInstance_NeverCreatesNewWindow ()
101221
{
102-
// Documents stakeholder decision:
103222
// When AllowOnlyOneInstance is true and no locked window exists,
104-
// files should load in most recent window, NOT create new window
223+
// files should load in most recent active window (Phase 2), NOT create new window
105224

106225
// This is the key fix for Issue #448
107226
// Before: NewWindowOrLockedWindow() would call NewWindow() when no locked window
@@ -110,5 +229,37 @@ public void AllowOnlyOneInstance_NeverCreatesNewWindow ()
110229
Assert.Pass("Behavior documented - NewWindow() should never be called");
111230
}
112231

232+
[Test]
233+
[Ignore("Documentation")]
234+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Test")]
235+
public void ActiveWindowTracking_Documentation ()
236+
{
237+
// LogTabWindow.OnLogTabWindowActivated() now calls LogExpertProxy.NotifyWindowActivated(this)
238+
// This updates _mostRecentActiveWindow in LogExpertProxy
239+
// LoadFiles() uses _mostRecentActiveWindow ?? _windowList[^1]
240+
241+
// Result: Files load in the window the user last clicked/focused,
242+
// not just the most recently created window
243+
244+
Assert.Pass("active window tracking documented");
245+
}
246+
247+
[Test]
248+
[Ignore("Documentation")]
249+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Test")]
250+
public void FallbackBehavior_Documentation ()
251+
{
252+
// Documents fallback behavior when no window has been activated:
253+
// If _mostRecentActiveWindow is null (no NotifyWindowActivated calls yet),
254+
// LoadFiles falls back to using _windowList[^1] (last created window)
255+
256+
// This ensures the feature works even if:
257+
// - App just started and no window has been focused yet
258+
// - All windows were closed and a new one created
259+
// - NotifyWindowActivated was never called for some reason
260+
261+
Assert.Pass("fallback behavior documented");
262+
}
263+
113264
#endregion
114265
}

src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2813,6 +2813,7 @@ private void OnLogTabWindowDeactivate (object sender, EventArgs e)
28132813
[SupportedOSPlatform("windows")]
28142814
private void OnLogTabWindowActivated (object sender, EventArgs e)
28152815
{
2816+
LogExpertProxy?.NotifyWindowActivated(this);
28162817
CurrentLogWindow?.AppFocusGained();
28172818
}
28182819

src/LogExpert/Classes/LogExpertProxy.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ internal class LogExpertProxy : ILogExpertProxy
1919

2020
[NonSerialized] private ILogTabWindow _firstLogTabWindow;
2121

22+
[NonSerialized] private ILogTabWindow _mostRecentActiveWindow; // ⭐ PHASE 2: Track most recently activated window
23+
2224
[NonSerialized] private int _logWindowIndex = 1;
2325

2426
#endregion
@@ -59,11 +61,24 @@ public LogExpertProxy (ILogTabWindow logTabWindow)
5961

6062
public void LoadFiles (string[] fileNames)
6163
{
62-
var logWin = _windowList[^1];
64+
// Use most recently ACTIVATED window, fallback to most recently created
65+
var logWin = _mostRecentActiveWindow ?? _windowList[^1];
66+
_logger.Info($"Loading files in {(_mostRecentActiveWindow != null ? "most recently activated" : "most recently created")} window");
6367
_ = logWin.Invoke(new MethodInvoker(logWin.SetForeground));
6468
logWin.LoadFiles(fileNames);
6569
}
6670

71+
/// <summary>
72+
/// Notifies the proxy that a window has been activated by the user.
73+
/// This is used to track which window should receive new files when "Allow Only One Instance" is enabled.
74+
/// </summary>
75+
/// <param name="window">The window that was activated</param>
76+
public void NotifyWindowActivated (ILogTabWindow window)
77+
{
78+
_mostRecentActiveWindow = window;
79+
_logger.Debug($"Most recent active window updated: {window}");
80+
}
81+
6782
[SupportedOSPlatform("windows")]
6883
public void NewWindow (string[] fileNames)
6984
{

0 commit comments

Comments
 (0)