Files
lmxopcua/tests/ZB.MOM.WW.LmxOpcUa.Client.UI.Tests/Screenshots/DateTimeRangePickerScreenshot.cs
Joseph Doherty 188cbf7d24 Add UI features, alarm ack, historian UTC fix, and Client.UI documentation
Major changes across the client stack:
- Settings persistence (connection, subscriptions, alarm source)
- Deferred OPC UA SDK init for instant startup
- Array/status code formatting, write value popup, alarm acknowledgment
- Severity-colored alarm rows, condition dedup on server side
- DateTimeRangePicker control with preset buttons and UTC text input
- Historian queries use wwTimezone=UTC and OPCQuality column
- Recursive subscribe from tree, multi-select remove
- Connection panel with expander, folder chooser for cert path
- Dynamic tab headers showing subscription/alarm counts
- Client.UI.md documentation with headless-rendered screenshots

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 20:46:45 -04:00

171 lines
5.2 KiB
C#

using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Media;
using Avalonia.Threading;
using Shouldly;
using Xunit;
using ZB.MOM.WW.LmxOpcUa.Client.UI.Controls;
namespace ZB.MOM.WW.LmxOpcUa.Client.UI.Tests.Screenshots;
public class DateTimeRangePickerScreenshot
{
private static readonly object Lock = new();
private static bool _initialized;
private static void EnsureInitialized()
{
lock (Lock)
{
if (_initialized) return;
_initialized = true;
AppBuilder.Configure<ScreenshotTestApp>()
.UseSkia()
.UseHeadless(new AvaloniaHeadlessPlatformOptions
{
UseHeadlessDrawing = false
})
.SetupWithoutStarting();
}
}
[Fact]
public void TextBoxes_ShowValues_WhenSetBeforeLoad()
{
EnsureInitialized();
Dispatcher.UIThread.Invoke(() =>
{
var picker = new DateTimeRangePicker
{
StartDateTime = new DateTimeOffset(2026, 3, 31, 8, 0, 0, TimeSpan.Zero),
EndDateTime = new DateTimeOffset(2026, 3, 31, 14, 0, 0, TimeSpan.Zero)
};
var window = new Window
{
Content = picker,
Width = 700, Height = 70
};
window.Show();
Dispatcher.UIThread.RunJobs();
var startInput = picker.FindControl<TextBox>("StartInput");
var endInput = picker.FindControl<TextBox>("EndInput");
startInput.ShouldNotBeNull();
endInput.ShouldNotBeNull();
startInput!.Text.ShouldBe("2026-03-31 08:00:00");
endInput!.Text.ShouldBe("2026-03-31 14:00:00");
window.Close();
});
}
[Fact]
public void TextBoxes_ShowValues_WhenSetAfterLoad()
{
EnsureInitialized();
Dispatcher.UIThread.Invoke(() =>
{
var picker = new DateTimeRangePicker();
var window = new Window
{
Content = picker,
Width = 700, Height = 70
};
window.Show();
Dispatcher.UIThread.RunJobs();
// Set values after the control is loaded
picker.StartDateTime = new DateTimeOffset(2026, 1, 15, 10, 30, 0, TimeSpan.Zero);
picker.EndDateTime = new DateTimeOffset(2026, 1, 15, 18, 45, 0, TimeSpan.Zero);
Dispatcher.UIThread.RunJobs();
var startInput = picker.FindControl<TextBox>("StartInput");
var endInput = picker.FindControl<TextBox>("EndInput");
startInput!.Text.ShouldBe("2026-01-15 10:30:00");
endInput!.Text.ShouldBe("2026-01-15 18:45:00");
window.Close();
});
}
[Fact]
public void PresetButtons_SetCorrectRange()
{
EnsureInitialized();
Dispatcher.UIThread.Invoke(() =>
{
var picker = new DateTimeRangePicker();
var window = new Window
{
Content = picker,
Width = 700, Height = 70
};
window.Show();
Dispatcher.UIThread.RunJobs();
// Click the 1h preset
var lastHourBtn = picker.FindControl<Button>("LastHourBtn");
lastHourBtn.ShouldNotBeNull();
lastHourBtn!.RaiseEvent(new Avalonia.Interactivity.RoutedEventArgs(Button.ClickEvent));
Dispatcher.UIThread.RunJobs();
picker.StartDateTime.ShouldNotBeNull();
picker.EndDateTime.ShouldNotBeNull();
var span = picker.EndDateTime!.Value - picker.StartDateTime!.Value;
span.TotalMinutes.ShouldBe(60, 1); // ~1 hour
// Verify text boxes are populated
var startInput = picker.FindControl<TextBox>("StartInput");
var endInput = picker.FindControl<TextBox>("EndInput");
startInput!.Text.ShouldNotBeNullOrEmpty();
endInput!.Text.ShouldNotBeNullOrEmpty();
window.Close();
});
}
[Fact]
public void Capture_DateTimeRangePicker_Screenshot()
{
EnsureInitialized();
Dispatcher.UIThread.Invoke(() =>
{
var picker = new DateTimeRangePicker
{
StartDateTime = new DateTimeOffset(2026, 3, 31, 8, 0, 0, TimeSpan.Zero),
EndDateTime = new DateTimeOffset(2026, 3, 31, 14, 0, 0, TimeSpan.Zero)
};
var window = new Window
{
Content = new Border { Padding = new Thickness(16), Background = Brushes.White, Child = picker },
Width = 700, Height = 70, Background = Brushes.White
};
window.Show();
Dispatcher.UIThread.RunJobs();
var bitmap = window.CaptureRenderedFrame();
Assert.NotNull(bitmap);
var outputDir = Path.Combine(
Path.GetDirectoryName(typeof(DateTimeRangePickerScreenshot).Assembly.Location)!, "screenshots");
Directory.CreateDirectory(outputDir);
bitmap!.Save(Path.Combine(outputDir, "datetimerangepicker.png"));
window.Close();
});
}
}