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
152 lines
7.9 KiB
C#
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;
|
|
}
|
|
}
|