using Avalonia; using Avalonia.Controls; using Avalonia.Headless; using Avalonia.Media; using Avalonia.Threading; using Xunit; using ZB.MOM.WW.LmxOpcUa.Client.UI.Controls; using ZB.MOM.WW.LmxOpcUa.Client.UI.ViewModels; using ZB.MOM.WW.LmxOpcUa.Client.UI.Views; using ZB.MOM.WW.LmxOpcUa.Client.UI.Services; using ZB.MOM.WW.LmxOpcUa.Client.UI.Tests.Fakes; using ZB.MOM.WW.LmxOpcUa.Client.Shared.Models; using BrowseResult = ZB.MOM.WW.LmxOpcUa.Client.Shared.Models.BrowseResult; namespace ZB.MOM.WW.LmxOpcUa.Client.UI.Tests.Screenshots; public class DocumentationScreenshots { private static readonly object Lock = new(); private static bool _initialized; private static void EnsureInitialized() { lock (Lock) { if (_initialized) return; _initialized = true; AppBuilder.Configure() .UseSkia() .UseHeadless(new AvaloniaHeadlessPlatformOptions { UseHeadlessDrawing = false }) .SetupWithoutStarting(); } } private static string OutputDir { get { var dir = Path.Combine( Path.GetDirectoryName(typeof(DocumentationScreenshots).Assembly.Location)!, "screenshots", "docs"); Directory.CreateDirectory(dir); return dir; } } [Fact] public void Capture_ConnectionPanel() { EnsureInitialized(); Dispatcher.UIThread.Invoke(() => { var vm = CreateConnectedViewModel(); var panel = new StackPanel { Spacing = 4, Children = { new StackPanel { Orientation = Avalonia.Layout.Orientation.Horizontal, Spacing = 8, Children = { new StackPanel { Spacing = 2, Children = { new TextBlock { Text = "Endpoint URL", FontSize = 11, Foreground = Brushes.Gray }, new TextBox { Text = "opc.tcp://localhost:4840/LmxOpcUa", Width = 400 } } }, new Button { Content = "Connect", Padding = new Thickness(16, 6) }, new Button { Content = "Disconnect", Padding = new Thickness(16, 6) } } } } }; var window = new Window { Content = new Border { Padding = new Thickness(12), Background = new SolidColorBrush(Color.Parse("#F0F0F0")), Child = panel }, Width = 700, Height = 60, Background = Brushes.White }; window.Show(); var bitmap = window.CaptureRenderedFrame(); bitmap?.Save(Path.Combine(OutputDir, "connection-panel.png")); window.Close(); }); } [Fact] public void Capture_SubscriptionsTab() { EnsureInitialized(); Dispatcher.UIThread.Invoke(() => { var service = new FakeOpcUaClientService(); var dispatcher = new SynchronousUiDispatcher(); var vm = new SubscriptionsViewModel(service, dispatcher) { IsConnected = true }; vm.ActiveSubscriptions.Add(new SubscriptionItemViewModel("ns=3;s=DEV.ScanState", 1000) { Value = "True", Status = "0x00000000 (Good)", Timestamp = "2026-03-31T14:30:00Z" }); vm.ActiveSubscriptions.Add(new SubscriptionItemViewModel("ns=3;s=TestMachine_001.TestHistoryValue", 1000) { Value = "3", Status = "0x00000000 (Good)", Timestamp = "2026-03-31T14:30:01Z" }); vm.ActiveSubscriptions.Add(new SubscriptionItemViewModel("ns=3;s=DevPlatform.CPULoad", 1000) { Value = "42.5", Status = "0x00000000 (Good)", Timestamp = "2026-03-31T14:30:02Z" }); var view = new SubscriptionsView { DataContext = vm }; var window = new Window { Content = view, Width = 750, Height = 250, Background = Brushes.White }; window.Show(); Dispatcher.UIThread.RunJobs(); var bitmap = window.CaptureRenderedFrame(); bitmap?.Save(Path.Combine(OutputDir, "subscriptions-tab.png")); window.Close(); }); } [Fact] public void Capture_AlarmsTab() { EnsureInitialized(); Dispatcher.UIThread.Invoke(() => { var service = new FakeOpcUaClientService(); var dispatcher = new SynchronousUiDispatcher(); var vm = new AlarmsViewModel(service, dispatcher) { IsConnected = true, IsSubscribed = true, MonitoredNodeIdText = "ns=3;s=TestArea" }; vm.AlarmEvents.Add(new AlarmEventViewModel("TestMachine_001.TestAlarm002", "TestAlarm002", 500, "Test alarm #2", true, true, true, new DateTime(2026, 3, 26, 17, 38, 22))); vm.AlarmEvents.Add(new AlarmEventViewModel("TestMachine_001.TestAlarm003", "TestAlarm003", 500, "Test alarm #3", true, true, false, new DateTime(2026, 3, 26, 17, 38, 22))); vm.AlarmEvents.Add(new AlarmEventViewModel("TestMachine_001.TestAlarm001", "TestAlarm001", 500, "Alarm cleared", true, false, false, DateTime.MinValue)); var view = new AlarmsView { DataContext = vm }; var window = new Window { Content = view, Width = 900, Height = 250, Background = Brushes.White }; window.Show(); Dispatcher.UIThread.RunJobs(); var bitmap = window.CaptureRenderedFrame(); bitmap?.Save(Path.Combine(OutputDir, "alarms-tab.png")); window.Close(); }); } [Fact] public void Capture_HistoryTab() { EnsureInitialized(); Dispatcher.UIThread.Invoke(() => { var service = new FakeOpcUaClientService(); var dispatcher = new SynchronousUiDispatcher(); var vm = new HistoryViewModel(service, dispatcher) { IsConnected = true, SelectedNodeId = "ns=3;s=TestMachine_001.TestHistoryValue", StartTime = new DateTimeOffset(2026, 3, 26, 0, 0, 0, TimeSpan.Zero), EndTime = new DateTimeOffset(2026, 3, 26, 18, 0, 0, TimeSpan.Zero) }; vm.Results.Add(new HistoryValueViewModel("0", "0x00000000 (Good)", "2026-03-26T04:02:59Z", "2026-03-26T04:02:59Z")); vm.Results.Add(new HistoryValueViewModel("7", "0x00000000 (Good)", "2026-03-26T04:22:13Z", "2026-03-26T04:22:13Z")); vm.Results.Add(new HistoryValueViewModel("4", "0x00000000 (Good)", "2026-03-26T04:22:16Z", "2026-03-26T04:22:16Z")); vm.Results.Add(new HistoryValueViewModel("9", "0x00000000 (Good)", "2026-03-26T05:08:46Z", "2026-03-26T05:08:46Z")); var view = new HistoryView { DataContext = vm }; var window = new Window { Content = view, Width = 800, Height = 350, Background = Brushes.White }; window.Show(); Dispatcher.UIThread.RunJobs(); var bitmap = window.CaptureRenderedFrame(); bitmap?.Save(Path.Combine(OutputDir, "history-tab.png")); window.Close(); }); } [Fact] public void Capture_DateTimeRangePicker() { EnsureInitialized(); Dispatcher.UIThread.Invoke(() => { var picker = new DateTimeRangePicker { StartDateTime = new DateTimeOffset(2026, 3, 26, 0, 0, 0, TimeSpan.Zero), EndDateTime = new DateTimeOffset(2026, 3, 31, 18, 0, 0, TimeSpan.Zero) }; var window = new Window { Content = new Border { Padding = new Thickness(12), Background = Brushes.White, Child = picker }, Width = 650, Height = 65, Background = Brushes.White }; window.Show(); Dispatcher.UIThread.RunJobs(); var bitmap = window.CaptureRenderedFrame(); bitmap?.Save(Path.Combine(OutputDir, "datetimerangepicker.png")); window.Close(); }); } private static MainWindowViewModel CreateConnectedViewModel() { var service = new FakeOpcUaClientService { ConnectResult = new ConnectionInfo("opc.tcp://localhost:4840/LmxOpcUa", "LmxOpcUa", "None", "http://opcfoundation.org/UA/SecurityPolicy#None", "session-1", "TestSession"), BrowseResults = new[] { new BrowseResult("ns=3;s=ZB", "ZB", "Object", true) }, RedundancyResult = new RedundancyInfo("Warm", 200, new[] { "urn:localhost:LmxOpcUa:instance1" }, "urn:localhost:LmxOpcUa:instance1") }; var factory = new FakeOpcUaClientServiceFactory(service); var settings = new FakeSettingsService(); return new MainWindowViewModel(factory, new SynchronousUiDispatcher(), settings); } }