Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/LogExpert.Tests/LogExpert.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<!-- Required by TabControllerTests to create themed DockPanel document tabs. -->
<PackageReference Include="DockPanelSuite.ThemeVS2015" />
</ItemGroup>

<ItemGroup Label="Data">
Expand Down
70 changes: 59 additions & 11 deletions src/LogExpert.Tests/Services/TabControllerTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
using System.Runtime.Versioning;

using LogExpert.Core.Config;
using LogExpert.Core.Entities;
using LogExpert.Core.Interfaces;
using LogExpert.UI.Controls.LogWindow;
using LogExpert.UI.Interface;
using LogExpert.UI.Services.TabControllerService;

using Moq;

using NUnit.Framework;

using WeifenLuo.WinFormsUI.Docking;
Expand All @@ -11,11 +17,8 @@ namespace LogExpert.Tests.Services;

/// <summary>
/// Unit tests for TabController.
/// Note: Many tests are limited because LogWindow is a complex WinForms control that cannot be easily mocked or
/// subclassed. Tests that require actual LogWindow instances would need to be run as integration tests with full UI
/// infrastructure.
/// These tests focus on the core TabController functionality that can be tested without instantiating LogWindow
/// objects.
/// Most tests focus on behavior that does not require LogWindow instances.
/// Tests that verify DockPanel tab order create real LogWindow instances through CreateLogWindow.
/// </summary>
[TestFixture]
[SupportedOSPlatform("windows")]
Expand All @@ -36,7 +39,10 @@ public void Setup ()
_dockPanel = new DockPanel
{
Dock = DockStyle.Fill,
DocumentStyle = DocumentStyle.DockingMdi
// Match LogExpert's document style; DockingMdi required test form to be an MDI container.
// Using DockingWindow both matches production behavior and allows the test to create real tabs without setting up an artificial MDI container.
DocumentStyle = DocumentStyle.DockingWindow,
Comment thread
Pr0metheus2 marked this conversation as resolved.
Theme = new VS2015LightTheme()
Comment thread
Pr0metheus2 marked this conversation as resolved.
};
_testForm.Controls.Add(_dockPanel);
_testForm.Show(); // Must show form for DockPanel to work
Expand Down Expand Up @@ -191,9 +197,53 @@ public void GetAllWindowsFromDockPanel_ReturnsReadOnlyList ()
Assert.That(result, Is.InstanceOf<IReadOnlyList<LogWindow>>());
}

[Test]
public void GetAllWindowsFromDockPanel_ReturnsDisplayedWindowsInTabOrder ()
{
// Arrange
using var firstWindow = CreateLogWindow("first.log");
using var secondWindow = CreateLogWindow("second.log");
using var thirdWindow = CreateLogWindow("third.log");

_tabController.AddWindow(firstWindow, "first.log");
_tabController.AddWindow(secondWindow, "second.log");
_tabController.AddWindow(thirdWindow, "third.log");

// Act
var result = _tabController.GetAllWindowsFromDockPanel();

// Assert
Assert.That(result, Is.EqualTo(new[] { firstWindow, secondWindow, thirdWindow }));
}

#endregion

#region GetAllWindows Tests
#region Helpers

private static LogWindow CreateLogWindow (string fileName)
Comment thread
Pr0metheus2 marked this conversation as resolved.
{
var coordinatorMock = new Mock<ILogWindowCoordinator>();
_ = coordinatorMock.Setup(coordinator => coordinator.ResolveHighlightGroup(It.IsAny<string?>(), It.IsAny<string?>())).Returns(new HighlightGroup());
_ = coordinatorMock.SetupGet(coordinator => coordinator.SearchParams).Returns(new SearchParams());

var configManagerMock = new Mock<IConfigManager>();
_ = configManagerMock.SetupGet(configManager => configManager.Settings).Returns(new Settings());

var pluginRegistryMock = new Mock<IPluginRegistry>();
_ = pluginRegistryMock.SetupGet(pluginRegistry => pluginRegistry.RegisteredColumnizers).Returns([new DefaultLogfileColumnizer()]);

return new LogWindow(
coordinatorMock.Object,
fileName,
isTempFile: false,
forcePersistenceLoading: false,
configManagerMock.Object,
pluginRegistryMock.Object);
}

#endregion

#region GetAllWindows Tests

[Test]
public void GetAllWindows_WhenEmpty_ReturnsEmptyList ()
Expand Down Expand Up @@ -315,10 +365,8 @@ public void AddWindow_WhenNotInitialized_ThrowsInvalidOperationException ()
// Arrange
using var controller = new TabController();

// Create a mock-like object that's not null to avoid ArgumentNullException
// We need to test that the "not initialized" check happens
// Unfortunately, LogWindow cannot be instantiated without its dependencies
// So we can only verify the ArgumentNullException is thrown first for null
// CreateLogWindow can now provide a non-null LogWindow when needed.
// This test verifies that null argument validation happens before the initialization check.
var ex = Assert.Throws<ArgumentNullException>(() => controller.AddWindow(null, "Test"));
Assert.That(ex.ParamName, Is.EqualTo("window"));
}
Expand Down
29 changes: 23 additions & 6 deletions src/LogExpert.UI/Services/TabControllerService/TabController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -461,12 +461,29 @@ protected virtual void Dispose (bool disposing)
/// <returns>Read-only list of all LogWindows in the DockPanel</returns>
public IReadOnlyList<LogWindow> GetAllWindowsFromDockPanel ()
{
return !_initialized || _dockPanel == null
? []
: _dockPanel.Panes
.SelectMany(pane => pane.DisplayingContents.OfType<LogWindow>())
.ToList()
.AsReadOnly();
if (!_initialized || _dockPanel == null)
{
return [];
}

var windows = new List<LogWindow>();

foreach (DockPane pane in _dockPanel.Panes)
Comment thread
Pr0metheus2 marked this conversation as resolved.
{
var displayingContents = pane.DisplayingContents;

// Use 'for' instead of 'foreach': DisplayingContents exposes displayed tabs through Count and its indexer.
// 'foreach' uses the inherited ReadOnlyCollection enumerator and does not return displayed tabs.
for (int index = 0; index < displayingContents.Count; index++)
Comment thread
Pr0metheus2 marked this conversation as resolved.
{
if (displayingContents[index] is LogWindow logWindow)
{
windows.Add(logWindow);
}
}
}

return windows.AsReadOnly();
}

#endregion
Expand Down
Loading