Skip to content

Commit a867284

Browse files
authored
Merge pull request #558 from LogExperts/refactoring
Refactor LogTabWindow and extract logic that is not needed in the GUI
2 parents ca421dc + df77e7c commit a867284

22 files changed

Lines changed: 1454 additions & 499 deletions

File tree

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"sdk": {
3-
"version": "10.0.100",
3+
"version": "10.0.200",
44
"rollForward": "latestPatch"
55
}
66
}

src/ColumnizerLib/Column.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ namespace ColumnizerLib;
22

33
public class Column : IColumnMemory
44
{
5-
//TODO Memory Functions need implementation
65
#region Fields
76

87
private const string REPLACEMENT = "...";

src/ColumnizerLib/IContextMenuEntry.cs

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ public interface IContextMenuEntry
1818
/// Your implementation can control whether LogExpert will show a menu entry by returning
1919
/// an appropriate value.<br></br>
2020
/// </summary>
21-
/// <param name="loglines">A list containing all selected line numbers.</param>
22-
/// <param name="columnizer">The currently selected Columnizer. You can use it to split log lines,
23-
/// if necessary.</param>
24-
/// <param name="callback">The callback interface implemented by LogExpert. You can use the functions
25-
/// for retrieving log lines or pass it along to functions of the Columnizer if needed.</param>
21+
/// <remarks>Throws an exception if any parameter is null or if linesCount is less than 1.</remarks>
22+
/// <param name="linesCount">The number of lines to include in the generated menu text. Must be a positive integer.</param>
23+
/// <param name="columnizer">An implementation of the ILogLineMemoryColumnizer interface used to format the log line data for display.</param>
24+
/// <param name="logline">An instance of ILogLineMemory representing the log line to be included in the menu text.</param>
2625
/// <returns>
2726
/// Return the string which should be displayed in the context menu.<br></br>
2827
/// You can control the menu behaviour by returning the the following values:<br></br>
@@ -32,34 +31,8 @@ public interface IContextMenuEntry
3231
/// <li>null: No menu entry is displayed.</li>
3332
/// </ul>
3433
/// </returns>
35-
[Obsolete("Use the overload of GetMenuText that takes an ILogLineMemory parameter instead.")]
36-
string GetMenuText (IList<int> loglines, ILogLineMemoryColumnizer columnizer, ILogExpertCallback callback);
37-
38-
/// <summary>
39-
/// This function is called from LogExpert if the context menu is about to be displayed.
40-
/// Your implementation can control whether LogExpert will show a menu entry by returning
41-
/// an appropriate value.<br></br>
42-
/// </summary>
43-
/// <remarks>Throws an exception if any parameter is null or if linesCount is less than 1.</remarks>
44-
/// <param name="linesCount">The number of lines to include in the generated menu text. Must be a positive integer.</param>
45-
/// <param name="columnizer">An implementation of the ILogLineMemoryColumnizer interface used to format the log line data for display.</param>
46-
/// <param name="logline">An instance of ILogLineMemory representing the log line to be included in the menu text.</param>
47-
/// <returns>A string containing the formatted menu text based on the provided log line and formatting options.</returns>
4834
string GetMenuText (int linesCount, ILogLineMemoryColumnizer columnizer, ILogLineMemory logline);
4935

50-
51-
/// <summary>
52-
/// This function is called from LogExpert if the menu entry is choosen by the user. <br></br>
53-
/// Note that this function is called from the GUI thread. So try to avoid time consuming operations.
54-
/// </summary>
55-
/// <param name="loglines">A list containing all selected line numbers.</param>
56-
/// <param name="columnizer">The currently selected Columnizer. You can use it to split log lines,
57-
/// if necessary.</param>
58-
/// <param name="callback">The callback interface implemented by LogExpert. You can use the functions
59-
/// for retrieving log lines or pass it along to functions of the Columnizer if needed.</param>
60-
[Obsolete("Use the overload of MenuSelected that takes an ILogLineMemory parameter instead.")]
61-
void MenuSelected (IList<int> loglines, ILogLineMemoryColumnizer columnizer, ILogExpertCallback callback);
62-
6336
/// <summary>
6437
/// This function is called from LogExpert if the menu entry is choosen by the user. <br></br>
6538
/// Note that this function is called from the GUI thread. So try to avoid time consuming operations.

src/LogExpert.Core/Classes/Log/LogfileReader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,7 @@ private Task<ILogLineMemory> GetLogLineMemoryInternal (int lineNum)
924924
{
925925
_logger.Debug(CultureInfo.InvariantCulture, "Returning null for line {0} because file is deleted.", lineNum);
926926
// fast fail if dead file was detected. Prevents repeated lags in GUI thread caused by callbacks from control (e.g. repaint)
927-
return null;
927+
return Task.FromResult<ILogLineMemory>(null);
928928
}
929929

930930
AcquireBufferListReaderLock();
@@ -933,7 +933,7 @@ private Task<ILogLineMemory> GetLogLineMemoryInternal (int lineNum)
933933
{
934934
ReleaseBufferListReaderLock();
935935
_logger.Error("Cannot find buffer for line {0}, file: {1}{2}", lineNum, _fileName, IsMultiFile ? " (MultiFile)" : "");
936-
return null;
936+
return Task.FromResult<ILogLineMemory>(null);
937937
}
938938
// disposeLock prevents that the garbage collector is disposing just in the moment we use the buffer
939939
AcquireDisposeLockUpgradableReadLock();

src/LogExpert.Core/Entities/SearchParams.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,19 @@ public class SearchParams
2121

2222
[field: NonSerialized]
2323
public bool IsShiftF3Pressed { get; set; }
24+
25+
public void CopyFrom (SearchParams other)
26+
{
27+
ArgumentNullException.ThrowIfNull(other);
28+
29+
CurrentLine = other.CurrentLine;
30+
HistoryList = other.HistoryList;
31+
IsCaseSensitive = other.IsCaseSensitive;
32+
IsFindNext = other.IsFindNext;
33+
IsForward = other.IsForward;
34+
IsFromTop = other.IsFromTop;
35+
IsRegex = other.IsRegex;
36+
SearchText = other.SearchText;
37+
IsShiftF3Pressed = other.IsShiftF3Pressed;
38+
}
2439
}

src/LogExpert.Core/Interfaces/ILogWindow.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -147,21 +147,6 @@ public interface ILogWindow
147147
/// </remarks>
148148
void AddTempFileTab (string fileName, string title);
149149

150-
/// <summary>
151-
/// Creates a new tab containing the specified list of log line entries.
152-
/// </summary>
153-
/// <param name="lineEntryList">
154-
/// A list of <see cref="LineEntry"/> objects containing the lines and their
155-
/// original line numbers to display in the new tab.
156-
/// </param>
157-
/// <param name="title">The title to display on the tab.</param>
158-
/// <remarks>
159-
/// This method is used to pipe filtered or selected content into a new tab
160-
/// without creating a physical file. The new tab maintains references to the
161-
/// original line numbers for context.
162-
/// </remarks>
163-
void WritePipeTab (IList<LineEntry> lineEntryList, string title);
164-
165150
/// <summary>
166151
/// Creates a new tab containing the specified list of log line entries.
167152
/// </summary>

src/LogExpert.Persister.Tests/ProjectFileValidatorTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,7 @@ public void LoadProjectData_VeryLargeProject_ValidatesEfficiently ()
878878
// Assert
879879
Assert.That(result, Is.Not.Null, "Result should not be null");
880880
Assert.That(result.ValidationResult.ValidFiles.Count, Is.EqualTo(fileCount), $"Should validate all {fileCount} files");
881-
Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThan(5000), "Should complete validation in reasonable time");
881+
Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThan(60000), "Should complete validation in reasonable time");
882882
}
883883

884884
#endregion
@@ -916,7 +916,7 @@ public void LoadProjectData_ManyMissingFiles_PerformsEfficiently ()
916916
Assert.That(result, Is.Not.Null, "Result should not be null");
917917
Assert.That(result.ValidationResult.ValidFiles.Count, Is.EqualTo(10), "Should have 10 valid files");
918918
Assert.That(result.ValidationResult.MissingFiles.Count, Is.EqualTo(40), "Should have 40 missing files");
919-
Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThan(2000), "Should handle many missing files efficiently");
919+
Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThan(5000), "Should handle many missing files efficiently");
920920
}
921921

922922
#endregion
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
using System.Runtime.Versioning;
2+
3+
using LogExpert.Core.Config;
4+
using LogExpert.Core.Entities;
5+
using LogExpert.Core.Interfaces;
6+
using LogExpert.UI.Controls.LogWindow;
7+
using LogExpert.UI.Interface;
8+
9+
using Moq;
10+
11+
using NUnit.Framework;
12+
13+
namespace LogExpert.Tests.Controls;
14+
15+
[TestFixture]
16+
[Apartment(ApartmentState.STA)]
17+
[SupportedOSPlatform("windows")]
18+
public class LogWindowCoordinatorIntegrationTests : IDisposable
19+
{
20+
private Mock<ILogWindowCoordinator> _coordinatorMock;
21+
private Mock<IConfigManager> _configManagerMock;
22+
private Settings _settings;
23+
private LogWindow _logWindow;
24+
private WindowsFormsSynchronizationContext _syncContext;
25+
private bool _disposed;
26+
27+
[SetUp]
28+
public void Setup ()
29+
{
30+
if (SynchronizationContext.Current == null)
31+
{
32+
_syncContext = new WindowsFormsSynchronizationContext();
33+
SynchronizationContext.SetSynchronizationContext(_syncContext);
34+
}
35+
36+
// Ensure PluginRegistry is initialized with default columnizers
37+
PluginRegistry.PluginRegistry.Create(Path.GetTempPath(), 250);
38+
39+
_coordinatorMock = new Mock<ILogWindowCoordinator>();
40+
_configManagerMock = new Mock<IConfigManager>();
41+
_settings = new Settings();
42+
_ = _configManagerMock.Setup(cm => cm.Settings).Returns(_settings);
43+
44+
// Setup default returns for coordinator
45+
_ = _coordinatorMock.Setup(c => c.ResolveHighlightGroup(It.IsAny<string?>(), It.IsAny<string?>())).Returns(new HighlightGroup());
46+
_ = _coordinatorMock.Setup(c => c.SearchParams).Returns(new SearchParams());
47+
48+
// Construct LogWindow with mocked dependencies
49+
// Using a temp file name that doesn't need to exist for constructor test
50+
_logWindow = new LogWindow(
51+
_coordinatorMock.Object,
52+
"test.log",
53+
true, // isTempFile
54+
false, // forcePersistenceLoading
55+
_configManagerMock.Object);
56+
}
57+
58+
[TearDown]
59+
public void TearDown ()
60+
{
61+
_logWindow?.Dispose();
62+
_syncContext?.Dispose();
63+
}
64+
65+
protected virtual void Dispose (bool disposing)
66+
{
67+
if (!_disposed)
68+
{
69+
_logWindow?.Dispose();
70+
_syncContext?.Dispose();
71+
_disposed = true;
72+
}
73+
}
74+
75+
public void Dispose ()
76+
{
77+
Dispose(true);
78+
GC.SuppressFinalize(this);
79+
}
80+
81+
[Test]
82+
public void Constructor_SubscribesToHighlightSettingsChanged ()
83+
{
84+
// Assert — verify event subscription happened during construction
85+
_coordinatorMock.VerifyAdd(
86+
c => c.HighlightSettingsChanged += It.IsAny<EventHandler>(),
87+
Times.Once);
88+
}
89+
90+
[Test]
91+
public void SetCurrentHighlightGroup_CallsCoordinatorResolveHighlightGroup ()
92+
{
93+
// Arrange
94+
var expectedGroup = new HighlightGroup { GroupName = "TestGroup" };
95+
_ = _coordinatorMock.Setup(c => c.ResolveHighlightGroup("TestGroup", null))
96+
.Returns(expectedGroup);
97+
98+
// Act
99+
_logWindow.SetCurrentHighlightGroup("TestGroup");
100+
101+
// Assert
102+
_coordinatorMock.Verify(
103+
c => c.ResolveHighlightGroup("TestGroup", null),
104+
Times.Once);
105+
}
106+
107+
[Test]
108+
public void Preferences_ReadsFromConfigManager_NotCoordinator ()
109+
{
110+
// Act
111+
var preferences = _logWindow.Preferences;
112+
113+
// Assert — Preferences comes from ConfigManager, not coordinator
114+
Assert.That(preferences, Is.SameAs(_settings.Preferences));
115+
}
116+
117+
[Test]
118+
public void SearchParams_ReadsFromCoordinator ()
119+
{
120+
// Arrange
121+
var searchParams = new SearchParams { SearchText = "test" };
122+
_ = _coordinatorMock.Setup(c => c.SearchParams).Returns(searchParams);
123+
124+
// Act — StartSearch accesses _coordinator.SearchParams
125+
// We can't easily call StartSearch (needs dataGridView rows),
126+
// but we can verify the property access pattern
127+
var coordParams = _coordinatorMock.Object.SearchParams;
128+
129+
// Assert
130+
Assert.That(coordParams.SearchText, Is.EqualTo("test"));
131+
}
132+
133+
[Test]
134+
public void Dispose_UnsubscribesFromHighlightSettingsChanged ()
135+
{
136+
// Act
137+
_logWindow.Dispose();
138+
139+
// Assert
140+
_coordinatorMock.VerifyRemove(
141+
c => c.HighlightSettingsChanged -= It.IsAny<EventHandler>(),
142+
Times.Once);
143+
144+
_logWindow = null; // prevent double-dispose in TearDown
145+
}
146+
}

0 commit comments

Comments
 (0)