Files
LlamaCasty/ytLive.Tests/LogsPanelTests.cs
T
gramps a6aea5bf26 feat(ui): color and acknowledge pinned log errors
Add independent AppLog message types and explicit Class 1/2 severity. Wrap and color log rows, pin Class 1 above Class 2, and let acknowledgement return entries to the current-launch timeline without changing file format or rotation.

Ref: https://logtape.org/manual/levels
2026-11-02 18:17:18 -08:00

152 lines
7.9 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Microsoft.Data.Sqlite;
using Xunit;
using ytLive.Helpers;
using ytLive.ViewModels;
namespace ytLive.Tests;
[Collection("RealApp")]
public sealed class LogsPanelTests
{
private readonly RealAppHost _app;
public LogsPanelTests(RealAppHost app) => _app = app;
[Fact]
public void NonEditPanel_DefaultsToLiveChat_AndShowsCurrentLaunchLogsNewestFirst()
{
var directory = Path.Combine(Path.GetTempPath(), $"ytlive-logs-panel-{Guid.NewGuid():N}");
Directory.CreateDirectory(directory);
var databasePath = Path.Combine(directory, "layout.db");
var logPath = Path.Combine(directory, "startup.log");
File.WriteAllText(logPath, "previous-launch-sentinel" + Environment.NewLine);
AppLog.PathOverride = logPath;
MainViewModel.LayoutPathOverride = databasePath;
try
{
_app.Run(() =>
{
var window = new MainWindow();
try
{
window.Show();
window.UpdateLayout();
var vm = (MainViewModel)window.DataContext;
vm.EditingActive = false;
window.UpdateLayout();
var panel = (UserControl)window.FindName("LeftPanel")!;
var chatTab = (ToggleButton)panel.FindName("LiveChatTabButton")!;
var logsTab = (ToggleButton)panel.FindName("LogsTabButton")!;
var logList = (ListBox)panel.FindName("LogListBox")!;
Assert.True(vm.IsLiveChatTabSelected);
Assert.True(chatTab.IsChecked);
Assert.False(logsTab.IsChecked);
Assert.False(logList.IsVisible);
vm.IsLogsTabSelected = true;
window.UpdateLayout();
Assert.True(logsTab.IsChecked);
Assert.False(chatTab.IsChecked);
Assert.True(logList.IsVisible);
AppLog.Write("LogsPanel older marker");
AppLog.Write("LogsPanel newer marker");
AppLog.WriteWarning("LogsPanel warning marker");
AppLog.WriteSuccess("LogsPanel success marker");
AppLog.Write("LogsPanel class 2 marker " + new string('2', 240),
AppLogMessageType.Error, AppLogErrorClass.Class2);
AppLog.Write("LogsPanel class 1 marker " + new string('1', 240),
AppLogMessageType.Error, AppLogErrorClass.Class1);
vm.RefreshCurrentLaunchLogs();
window.UpdateLayout();
var entries = vm.CurrentLaunchLogEntries.Select(entry => entry.Text).ToArray();
var newerIndex = Array.FindIndex(entries, text => text.Contains("LogsPanel newer marker", StringComparison.Ordinal));
var olderIndex = Array.FindIndex(entries, text => text.Contains("LogsPanel older marker", StringComparison.Ordinal));
Assert.True(newerIndex >= 0 && olderIndex >= 0, "new log writes did not reach the Logs tab");
Assert.True(newerIndex < olderIndex, "the Logs tab must put the newest entry first");
Assert.DoesNotContain(entries, text => text.Contains("previous-launch-sentinel", StringComparison.Ordinal));
Assert.Same(vm.CurrentLaunchLogEntries, logList.ItemsSource);
var class1 = vm.PinnedLogEntries.Single(entry => entry.Text.Contains("LogsPanel class 1 marker", StringComparison.Ordinal));
var class2 = vm.PinnedLogEntries.Single(entry => entry.Text.Contains("LogsPanel class 2 marker", StringComparison.Ordinal));
Assert.True(Array.IndexOf(vm.PinnedLogEntries.ToArray(), class1)
< Array.IndexOf(vm.PinnedLogEntries.ToArray(), class2));
Assert.Equal("CLASS 1", class1.ErrorClassLabel);
Assert.Equal("CLASS 2", class2.ErrorClassLabel);
Assert.Equal("CLASS 1", class1.TypeLabel);
var pinnedPanel = (ItemsControl)panel.FindName("PinnedLogPanel")!;
Assert.Equal(System.Windows.Visibility.Visible, pinnedPanel.Visibility);
var class1Text = Find(pinnedPanel, element => element is TextBlock text
&& text.Text.Contains("LogsPanel class 1 marker", StringComparison.Ordinal)) as TextBlock;
var class2Text = Find(pinnedPanel, element => element is TextBlock text
&& text.Text.Contains("LogsPanel class 2 marker", StringComparison.Ordinal)) as TextBlock;
Assert.NotNull(class1Text);
Assert.NotNull(class2Text);
Assert.Equal(TextWrapping.Wrap, class1Text!.TextWrapping);
Assert.Equal(BrushColor("Brush.Error"), Assert.IsType<SolidColorBrush>(class1Text.Foreground).Color);
Assert.Equal(BrushColor("Brush.Attention"), Assert.IsType<SolidColorBrush>(class2Text!.Foreground).Color);
var warningText = Find(logList, element => element is TextBlock text
&& text.Text.Contains("LogsPanel warning marker", StringComparison.Ordinal)) as TextBlock;
var successText = Find(logList, element => element is TextBlock text
&& text.Text.Contains("LogsPanel success marker", StringComparison.Ordinal)) as TextBlock;
var infoText = Find(logList, element => element is TextBlock text
&& text.Text.Contains("LogsPanel newer marker", StringComparison.Ordinal)) as TextBlock;
Assert.NotNull(warningText);
Assert.NotNull(successText);
Assert.NotNull(infoText);
Assert.Equal(BrushColor("Brush.Secondary"), Assert.IsType<SolidColorBrush>(warningText!.Foreground).Color);
Assert.Equal(BrushColor("Brush.Positive"), Assert.IsType<SolidColorBrush>(successText!.Foreground).Color);
Assert.Equal(BrushColor("Brush.OnSurfaceVariant"), Assert.IsType<SolidColorBrush>(infoText!.Foreground).Color);
var class1Ack = Find(pinnedPanel, element => element is Button button
&& button.DataContext is AppLogEntry entry && entry.Sequence == class1.Sequence) as Button;
Assert.NotNull(class1Ack?.Command);
Assert.Same(class1, class1Ack!.CommandParameter);
class1Ack!.Command!.Execute(class1Ack.CommandParameter);
Assert.DoesNotContain(class1, vm.PinnedLogEntries);
Assert.Contains(class1, vm.CurrentLaunchLogEntries);
}
finally
{
window.Close();
}
});
}
finally
{
MainViewModel.LayoutPathOverride = null;
AppLog.PathOverride = null;
SqliteConnection.ClearAllPools();
try { Directory.Delete(directory, recursive: true); } catch { }
}
}
private static System.Windows.Media.Color BrushColor(string key)
=> Assert.IsType<SolidColorBrush>(Application.Current!.FindResource(key)).Color;
private static DependencyObject? Find(DependencyObject parent, Func<DependencyObject, bool> predicate)
{
if (predicate(parent)) return parent;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (predicate(child)) return child;
var found = Find(child, predicate);
if (found != null) return found;
}
return null;
}
}