using XFEWorkflow.Abstractions;
using XFEWorkflow.Core.Runtime;
using XFEWorkflow.ViewModels;
using XFEWorkflow.Views;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Automation.Provider;
using Microsoft.UI.Xaml.Media;
namespace XFEWorkflow.Services;
internal static partial class UiSmokeTest
{
private static async Task SmokeWorkflowDocumentsAsync(string output)
{
var services = AppServices.Current; var navigation = AppShellPage.Current!.ViewModel.NavigationViewService;
var root = (FrameworkElement)App.MainWindow.Content; var originalSize = App.MainWindow.AppWindow.Size; var originalTheme = root.RequestedTheme;
var paletteSize = services.State.PaneSize("workflow.palette", 260);
var outputSize = services.State.PaneSize("workflow.output", 156);
var scale = root.XamlRoot.RasterizationScale; App.MainWindow.AppWindow.Resize(new((int)(1600 * scale), (int)(1000 * scale)));
var first = WorkflowTemplates.Welcome(); first.Name = "标签页 A"; first.Enabled = false; first.Triggers.Add(new() { Kind = TriggerKind.Interval, Enabled = true, IntervalSeconds = 3600 });
var second = WorkflowTemplates.Welcome(); second.Name = "标签页 B"; second.Enabled = false;
await services.Workflows.SaveAsync(first); await services.Workflows.SaveAsync(second);
try
{
navigation.NavigateTo<WorkflowListPage>(); await WaitManagementAsync(() => Descendants<Frame>(App.MainWindow.Content).Any(x => x.Content is WorkflowListPage));
await WaitManagementAsync(() => Descendants<Button>(App.MainWindow.Content).Any(x => AutomationProperties.GetAutomationId(x) == "EditWorkflow-" + first.Id));
var firstRow = Descendants<Expander>(App.MainWindow.Content).Single(x => x.Tag as string == first.Id); firstRow.IsExpanded = false;
await Task.Delay(300); await CaptureWindowAsync(Path.Combine(output, "workflow-list.png"));
await InvokeButtonAsync(Descendants<Button>(firstRow).Single(x => AutomationProperties.GetAutomationId(x) == "EditWorkflow-" + first.Id));
await WaitManagementAsync(() => WorkflowEditorPage.Current?.ActiveEditor?.ViewModel.SelectedWorkflow?.Id == first.Id);
if (firstRow.IsExpanded) throw new InvalidOperationException("列表右侧编辑按钮不应展开工作流卡片");
var editorNavigation = Descendants<NavigationViewItem>(AppShellPage.Current!).Single(x => x.Content as string == "工作流编辑器");
if (editorNavigation.Icon is not FontIcon { Glyph: "\uE943" }) throw new InvalidOperationException("工作流编辑器入口没有使用代码图标");
var hub = WorkflowEditorPage.Current!; var a = hub.ActiveEditor!; a.ViewModel.SelectedWorkflow!.Description = "A 独立修改";
var b = await hub.OpenAsync(second.Id); b.ViewModel.SelectedWorkflow!.Description = "B 独立修改";
await hub.OpenAsync(first.Id);
if (!ReferenceEquals(hub.ActiveEditor, a) || hub.Documents.TabItems.Count != 2 || a.ViewModel.SelectedWorkflow!.Description != "A 独立修改") throw new InvalidOperationException("多标签页未保留独立编辑状态或重复打开");
if (Descendants<CommandBar>(a).Any(x => x.Visibility == Visibility.Visible) || Descendants<Expander>(a).Any(x => x.Name == "WorkflowLibraryExpander" && x.Visibility == Visibility.Visible)) throw new InvalidOperationException("编辑器仍展示工作流列表和旧溢出工具栏");
if (!Descendants<MenuBar>(hub).Single().Items.Select(x => x.Title).SequenceEqual(new[] { "文件", "编辑", "工作流", "视图", "工具" })) throw new InvalidOperationException("顶部菜单不完整");
if (!a.ViewModel.PaletteGroups.Any(x => x.Name == "鼠标操作" && x.Glyph == "\uE962") || !a.ViewModel.PaletteGroups.Any(x => x.Name == "键盘操作" && x.Glyph == "\uE765")) throw new InvalidOperationException("鼠标或键盘分类图标错误");
var dockingEvidence = new List<object>(); var colors = new Dictionary<ElementTheme, Windows.UI.Color>();
async Task VerifyDockingAsync(string stage)
{
await Task.Delay(200);
var palette = Descendants<Border>(hub).Single(x => x.Name == "PalettePanel");
var header = Descendants<Grid>(hub.ActiveEditor!).Single(x => x.Name == "EditorModeHeader");
var selected = (TabViewItem)hub.Documents.SelectedItem;
var stripOrigin = hub.Documents.TransformToVisual(hub).TransformPoint(new(0, 0));
var headerOrigin = header.TransformToVisual(hub).TransformPoint(new(0, 0));
var tabBottom = selected.TransformToVisual(hub).TransformPoint(new(0, selected.ActualHeight)).Y;
var paletteOrigin = palette.TransformToVisual(hub).TransformPoint(new(0, 0));
if (Math.Abs(stripOrigin.X - headerOrigin.X) > 1 || Math.Abs(tabBottom - headerOrigin.Y) > 1 || stripOrigin.X - paletteOrigin.X < 200 || Math.Abs(paletteOrigin.Y - stripOrigin.Y) > 2)
throw new InvalidOperationException($"工作流标签与编辑区没有对齐或存在间隙:{stage} · strip={stripOrigin} · header={headerOrigin} · tabBottom={tabBottom}");
var outputPanel = Descendants<Border>(hub).Single(x => x.Name == "OutputPanel");
var outputOrigin = outputPanel.TransformToVisual(hub).TransformPoint(new(0, 0));
var documentBottom = hub.DocumentSurface.TransformToVisual(hub).TransformPoint(new(0, hub.DocumentSurface.ActualHeight)).Y;
if (Descendants<Border>(hub.DocumentSurface).Contains(outputPanel) || Math.Abs(outputOrigin.X - hub.DocumentSurface.TransformToVisual(hub).TransformPoint(new(0, 0)).X) > 1 || Math.Abs(outputOrigin.Y - documentBottom - 8) > 1)
throw new InvalidOperationException("运行输出没有与上方编辑窗口分离:" + stage);
if (VisualTreeHelper.GetParent(outputPanel) != VisualTreeHelper.GetParent(hub.DocumentSurface) || VisualTreeHelper.GetParent(palette) != VisualTreeHelper.GetParent(outputPanel))
throw new InvalidOperationException("输出、编辑器和节点库应为相互独立的面板");
var selectedPath = Descendants<Microsoft.UI.Xaml.Shapes.Path>(selected).Single(x => x.Name == "SelectedBackgroundPath");
var color = ((SolidColorBrush)header.Background).Color;
if (selectedPath.Fill is not SolidColorBrush fill || fill.Color != color)
throw new InvalidOperationException("工作流选项卡与内部编辑窗口未形成统一主题背景:" + stage);
if (hub.DocumentSurface.Background is not SolidColorBrush surface || palette.Background is not SolidColorBrush paletteBrush || surface.Color != paletteBrush.Color || surface.Color == color)
throw new InvalidOperationException("编辑器外层应使用与节点库相同的控件背景:" + stage);
var status = Descendants<Border>(hub).Single(x => x.Name == "EditorStatus");
if (status.BorderThickness.Left < 1 || status.CornerRadius.TopLeft < 4 || status.Padding.Top < 4)
throw new InvalidOperationException("底部状态输出未包裹在控件中:" + stage);
colors[root.RequestedTheme] = color;
dockingEvidence.Add(new { stage, theme = root.RequestedTheme.ToString(), stripLeft = stripOrigin.X, headerLeft = headerOrigin.X, tabBottom, headerTop = headerOrigin.Y, paletteWidth = palette.ActualWidth, documentBottom, outputTop = outputOrigin.Y, outputHeight = outputPanel.ActualHeight, background = color.ToString() });
}
root.RequestedTheme = ElementTheme.Dark; await VerifyDockingAsync("dark");
var initialViewport = Descendants<Controls.WorkflowCanvas>(a).Single().InspectViewport();
var startX = initialViewport["startScreenX"]!.GetValue<double>();
if (startX < 0 || startX > initialViewport["viewportWidth"]!.GetValue<double>()) throw new InvalidOperationException("首次打开编辑器未将开始节点置于可见范围:" + initialViewport.ToJsonString());
var dockedSplitter = Descendants<Controls.PaneSplitter>(hub).Single(x => AutomationProperties.GetName(x) == "调整节点库宽度");
var oldPaletteWidth = Descendants<Border>(hub).Single(x => x.Name == "PalettePanel").ActualWidth;
services.State.SetPaneSize("workflow.palette", oldPaletteWidth + 48); dockedSplitter.ReloadPreference(); await VerifyDockingAsync("resized");
if (Math.Abs(Descendants<Border>(hub).Single(x => x.Name == "PalettePanel").ActualWidth - oldPaletteWidth - 48) > 4) throw new InvalidOperationException("调整节点库宽度后标签与编辑器未随动");
await hub.OpenAsync(second.Id); await VerifyDockingAsync("second-tab");
await hub.OpenAsync(first.Id); await VerifyDockingAsync("first-tab-restored");
var outputTabs = Descendants<TabView>(hub).Single(x => x.Name == "RunPanel"); outputTabs.SelectedIndex = 1;
var outputSplitter = Descendants<Controls.PaneSplitter>(hub).Single(x => AutomationProperties.GetName(x) == "调整运行输出高度");
services.State.SetPaneSize("workflow.output", 220); outputSplitter.ReloadPreference(); await VerifyDockingAsync("output-resized");
if (Math.Abs(Descendants<Border>(hub).Single(x => x.Name == "OutputPanel").ActualHeight - 220) > 1) throw new InvalidOperationException("独立输出面板不能调整高度");
await a.ExecuteEditorCommandAsync("output"); await Task.Delay(100);
if (Math.Abs(Descendants<Border>(hub).Single(x => x.Name == "OutputPanel").ActualHeight - 42) > 1) throw new InvalidOperationException("独立输出面板未收起");
await hub.OpenAsync(second.Id); await VerifyDockingAsync("output-other-tab");
if (Descendants<TabView>(hub).Single(x => x.Name == "RunPanel").SelectedIndex != 0) throw new InvalidOperationException("工作流输出标签状态相互覆盖");
await hub.OpenAsync(first.Id); await Task.Delay(100);
if (!ReferenceEquals(outputTabs, Descendants<TabView>(hub).Single(x => x.Name == "RunPanel")) || outputTabs.SelectedIndex != 1 || Math.Abs(Descendants<Border>(hub).Single(x => x.Name == "OutputPanel").ActualHeight - 42) > 1)
throw new InvalidOperationException("切回工作流未恢复输出标签和收起状态");
await a.ExecuteEditorCommandAsync("output"); outputTabs.SelectedIndex = 0; await VerifyDockingAsync("output-restored");
await CaptureWindowAsync(Path.Combine(output, "workflow-editor-connected-tabs-dark.png"));
root.RequestedTheme = ElementTheme.Light; await VerifyDockingAsync("light");
await CaptureWindowAsync(Path.Combine(output, "workflow-editor-connected-tabs-light.png"));
if (colors[ElementTheme.Dark] == colors[ElementTheme.Light]) throw new InvalidOperationException("工作流编辑区没有适配深浅主题");
await Infrastructure.Storage.AtomicJson.WriteAsync(Path.Combine(output, "workflow-document-docking.json"), dockingEvidence);
root.RequestedTheme = originalTheme;
var search = Descendants<TextBox>(hub).Single(x => x.Name == "PaletteSearch"); search.Text = "输出日志";
await WaitManagementAsync(() => a.ViewModel.PaletteRows.OfType<PaletteItem>().Any(x => x.Descriptor.Id == "core.log"));
var paletteList = Descendants<ListView>(hub).Single(x => x.Name == "PaletteList"); var logItem = a.ViewModel.PaletteRows.OfType<PaletteItem>().Single(x => x.Descriptor.Id == "core.log");
paletteList.ScrollIntoView(logItem); await WaitManagementAsync(() => paletteList.ContainerFromItem(logItem) is FrameworkElement);
var nodeCount = a.ViewModel.SelectedWorkflow!.Graph.Nodes.Count;
var container = (ListViewItem)paletteList.ContainerFromItem(logItem);
var peer = FrameworkElementAutomationPeer.FromElement(container) ?? new ListViewItemAutomationPeer(container);
if (peer.GetPattern(PatternInterface.Invoke) is not IInvokeProvider invoke) throw new InvalidOperationException("节点库项缺少自动化调用入口");
invoke.Invoke();
await WaitManagementAsync(() => a.ViewModel.SelectedWorkflow!.Graph.Nodes.Count == nodeCount + 1);
await a.ExecuteEditorCommandAsync("undo"); if (a.ViewModel.SelectedWorkflow!.Graph.Nodes.Count != nodeCount) throw new InvalidOperationException("独立节点库添加与撤销未作用到当前工作流"); search.Text = "";
await services.Workflows.SetActivationAsync(first.Id, true, 9); await hub.SaveAllAsync();
if ((await services.Workflows.GetAsync(first.Id)).Enabled != true) throw new InvalidOperationException("编辑器保存覆盖了列表的启停状态");
await CaptureWindowAsync(Path.Combine(output, "workflow-editor-multiple-tabs-menus.png"));
navigation.NavigateTo<WorkflowListPage>(); await Task.Delay(300);
var frame = Descendants<Frame>(App.MainWindow.Content).Single(x => x.Content is WorkflowListPage); var list = (WorkflowListPage)frame.Content;
await list.SetEnabledAsync(first.Id, false);
if (services.Scheduler.StatusFor(await services.Workflows.GetAsync(first.Id)).Any(x => x.Enabled)) throw new InvalidOperationException("列表关闭后仍在调度");
navigation.NavigateTo<WorkflowEditorPage>(); await Task.Delay(150); if (hub.Documents.TabItems.Count != 2) throw new InvalidOperationException("离开编辑器后标签页丢失");
}
finally
{
await WorkflowEditorPage.CloseOpenAsync(first.Id); await WorkflowEditorPage.CloseOpenAsync(second.Id); navigation.NavigateTo<MainPage>();
services.State.SetPaneSize("workflow.palette", paletteSize); services.State.SetPaneSize("workflow.output", outputSize); await services.State.SaveAsync(); root.RequestedTheme = originalTheme; App.MainWindow.AppWindow.Resize(originalSize);
}
}
}
using XFEWorkflow.Abstractions;
using XFEWorkflow.Core.Runtime;
using XFEWorkflow.ViewModels;
using XFEWorkflow.Views;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Automation.Provider;
using Microsoft.UI.Xaml.Media;
namespace XFEWorkflow.Services;
internal static partial class UiSmokeTest
{
private static async Task SmokeWorkflowDocumentsAsync(string output)
{
var services = AppServices.Current; var navigation = AppShellPage.Current!.ViewModel.NavigationViewService;
var root = (FrameworkElement)App.MainWindow.Content; var originalSize = App.MainWindow.AppWindow.Size; var originalTheme = root.RequestedTheme;
var paletteSize = services.State.PaneSize("workflow.palette", 260);
var outputSize = services.State.PaneSize("workflow.output", 156);
var scale = root.XamlRoot.RasterizationScale; App.MainWindow.AppWindow.Resize(new((int)(1600 * scale), (int)(1000 * scale)));
var first = WorkflowTemplates.Welcome(); first.Name = "标签页 A"; first.Enabled = false; first.Triggers.Add(new() { Kind = TriggerKind.Interval, Enabled = true, IntervalSeconds = 3600 });
var second = WorkflowTemplates.Welcome(); second.Name = "标签页 B"; second.Enabled = false;
await services.Workflows.SaveAsync(first); await services.Workflows.SaveAsync(second);
try
{
navigation.NavigateTo<WorkflowListPage>(); await WaitManagementAsync(() => Descendants<Frame>(App.MainWindow.Content).Any(x => x.Content is WorkflowListPage));
await WaitManagementAsync(() => Descendants<Button>(App.MainWindow.Content).Any(x => AutomationProperties.GetAutomationId(x) == "EditWorkflow-" + first.Id));
var firstRow = Descendants<Expander>(App.MainWindow.Content).Single(x => x.Tag as string == first.Id); firstRow.IsExpanded = false;
await Task.Delay(300); await CaptureWindowAsync(Path.Combine(output, "workflow-list.png"));
await InvokeButtonAsync(Descendants<Button>(firstRow).Single(x => AutomationProperties.GetAutomationId(x) == "EditWorkflow-" + first.Id));
await WaitManagementAsync(() => WorkflowEditorPage.Current?.ActiveEditor?.ViewModel.SelectedWorkflow?.Id == first.Id);
if (firstRow.IsExpanded) throw new InvalidOperationException("列表右侧编辑按钮不应展开工作流卡片");
var editorNavigation = Descendants<NavigationViewItem>(AppShellPage.Current!).Single(x => x.Content as string == "工作流编辑器");
if (editorNavigation.Icon is not FontIcon { Glyph: "\uE943" }) throw new InvalidOperationException("工作流编辑器入口没有使用代码图标");
var hub = WorkflowEditorPage.Current!; var a = hub.ActiveEditor!; a.ViewModel.SelectedWorkflow!.Description = "A 独立修改";
var b = await hub.OpenAsync(second.Id); b.ViewModel.SelectedWorkflow!.Description = "B 独立修改";
await hub.OpenAsync(first.Id);
if (!ReferenceEquals(hub.ActiveEditor, a) || hub.Documents.TabItems.Count != 2 || a.ViewModel.SelectedWorkflow!.Description != "A 独立修改") throw new InvalidOperationException("多标签页未保留独立编辑状态或重复打开");
if (Descendants<CommandBar>(a).Any(x => x.Visibility == Visibility.Visible) || Descendants<Expander>(a).Any(x => x.Name == "WorkflowLibraryExpander" && x.Visibility == Visibility.Visible)) throw new InvalidOperationException("编辑器仍展示工作流列表和旧溢出工具栏");
if (!Descendants<MenuBar>(hub).Single().Items.Select(x => x.Title).SequenceEqual(new[] { "文件", "编辑", "工作流", "视图", "工具" })) throw new InvalidOperationException("顶部菜单不完整");
if (!a.ViewModel.PaletteGroups.Any(x => x.Name == "鼠标操作" && x.Glyph == "\uE962") || !a.ViewModel.PaletteGroups.Any(x => x.Name == "键盘操作" && x.Glyph == "\uE765")) throw new InvalidOperationException("鼠标或键盘分类图标错误");
var dockingEvidence = new List<object>(); var colors = new Dictionary<ElementTheme, Windows.UI.Color>();
async Task VerifyDockingAsync(string stage)
{
await Task.Delay(200);
var palette = Descendants<Border>(hub).Single(x => x.Name == "PalettePanel");
var header = Descendants<Grid>(hub.ActiveEditor!).Single(x => x.Name == "EditorModeHeader");
var selected = (TabViewItem)hub.Documents.SelectedItem;
var stripOrigin = hub.Documents.TransformToVisual(hub).TransformPoint(new(0, 0));
var headerOrigin = header.TransformToVisual(hub).TransformPoint(new(0, 0));
var tabBottom = selected.TransformToVisual(hub).TransformPoint(new(0, selected.ActualHeight)).Y;
var paletteOrigin = palette.TransformToVisual(hub).TransformPoint(new(0, 0));
if (Math.Abs(stripOrigin.X - headerOrigin.X) > 1 || Math.Abs(tabBottom - headerOrigin.Y) > 1 || stripOrigin.X - paletteOrigin.X < 200 || Math.Abs(paletteOrigin.Y - stripOrigin.Y) > 2)
throw new InvalidOperationException($"工作流标签与编辑区没有对齐或存在间隙:{stage} · strip={stripOrigin} · header={headerOrigin} · tabBottom={tabBottom}");
var outputPanel = Descendants<Border>(hub).Single(x => x.Name == "OutputPanel");
var outputOrigin = outputPanel.TransformToVisual(hub).TransformPoint(new(0, 0));
var documentBottom = hub.DocumentSurface.TransformToVisual(hub).TransformPoint(new(0, hub.DocumentSurface.ActualHeight)).Y;
if (Descendants<Border>(hub.DocumentSurface).Contains(outputPanel) || Math.Abs(outputOrigin.X - hub.DocumentSurface.TransformToVisual(hub).TransformPoint(new(0, 0)).X) > 1 || Math.Abs(outputOrigin.Y - documentBottom - 8) > 1)
throw new InvalidOperationException("运行输出没有与上方编辑窗口分离:" + stage);
if (VisualTreeHelper.GetParent(outputPanel) != VisualTreeHelper.GetParent(hub.DocumentSurface) || VisualTreeHelper.GetParent(palette) != VisualTreeHelper.GetParent(outputPanel))
throw new InvalidOperationException("输出、编辑器和节点库应为相互独立的面板");
var selectedPath = Descendants<Microsoft.UI.Xaml.Shapes.Path>(selected).Single(x => x.Name == "SelectedBackgroundPath");
var color = ((SolidColorBrush)header.Background).Color;
if (selectedPath.Fill is not SolidColorBrush fill || fill.Color != color)
throw new InvalidOperationException("工作流选项卡与内部编辑窗口未形成统一主题背景:" + stage);
if (hub.DocumentSurface.Background is not SolidColorBrush surface || palette.Background is not SolidColorBrush paletteBrush || surface.Color != paletteBrush.Color || surface.Color == color)
throw new InvalidOperationException("编辑器外层应使用与节点库相同的控件背景:" + stage);
var status = Descendants<Border>(hub).Single(x => x.Name == "EditorStatus");
if (status.BorderThickness.Left < 1 || status.CornerRadius.TopLeft < 4 || status.Padding.Top < 4)
throw new InvalidOperationException("底部状态输出未包裹在控件中:" + stage);
colors[root.RequestedTheme] = color;
dockingEvidence.Add(new { stage, theme = root.RequestedTheme.ToString(), stripLeft = stripOrigin.X, headerLeft = headerOrigin.X, tabBottom, headerTop = headerOrigin.Y, paletteWidth = palette.ActualWidth, documentBottom, outputTop = outputOrigin.Y, outputHeight = outputPanel.ActualHeight, background = color.ToString() });
}
root.RequestedTheme = ElementTheme.Dark; await VerifyDockingAsync("dark");
var initialViewport = Descendants<Controls.WorkflowCanvas>(a).Single().InspectViewport();
var startX = initialViewport["startScreenX"]!.GetValue<double>();
if (startX < 0 || startX > initialViewport["viewportWidth"]!.GetValue<double>()) throw new InvalidOperationException("首次打开编辑器未将开始节点置于可见范围:" + initialViewport.ToJsonString());
var dockedSplitter = Descendants<Controls.PaneSplitter>(hub).Single(x => AutomationProperties.GetName(x) == "调整节点库宽度");
var oldPaletteWidth = Descendants<Border>(hub).Single(x => x.Name == "PalettePanel").ActualWidth;
services.State.SetPaneSize("workflow.palette", oldPaletteWidth + 48); dockedSplitter.ReloadPreference(); await VerifyDockingAsync("resized");
if (Math.Abs(Descendants<Border>(hub).Single(x => x.Name == "PalettePanel").ActualWidth - oldPaletteWidth - 48) > 4) throw new InvalidOperationException("调整节点库宽度后标签与编辑器未随动");
await hub.OpenAsync(second.Id); await VerifyDockingAsync("second-tab");
await hub.OpenAsync(first.Id); await VerifyDockingAsync("first-tab-restored");
var outputTabs = Descendants<TabView>(hub).Single(x => x.Name == "RunPanel"); outputTabs.SelectedIndex = 1;
var outputSplitter = Descendants<Controls.PaneSplitter>(hub).Single(x => AutomationProperties.GetName(x) == "调整运行输出高度");
services.State.SetPaneSize("workflow.output", 220); outputSplitter.ReloadPreference(); await VerifyDockingAsync("output-resized");
if (Math.Abs(Descendants<Border>(hub).Single(x => x.Name == "OutputPanel").ActualHeight - 220) > 1) throw new InvalidOperationException("独立输出面板不能调整高度");
await a.ExecuteEditorCommandAsync("output"); await Task.Delay(100);
if (Math.Abs(Descendants<Border>(hub).Single(x => x.Name == "OutputPanel").ActualHeight - 42) > 1) throw new InvalidOperationException("独立输出面板未收起");
await hub.OpenAsync(second.Id); await VerifyDockingAsync("output-other-tab");
if (Descendants<TabView>(hub).Single(x => x.Name == "RunPanel").SelectedIndex != 0) throw new InvalidOperationException("工作流输出标签状态相互覆盖");
await hub.OpenAsync(first.Id); await Task.Delay(100);
if (!ReferenceEquals(outputTabs, Descendants<TabView>(hub).Single(x => x.Name == "RunPanel")) || outputTabs.SelectedIndex != 1 || Math.Abs(Descendants<Border>(hub).Single(x => x.Name == "OutputPanel").ActualHeight - 42) > 1)
throw new InvalidOperationException("切回工作流未恢复输出标签和收起状态");
await a.ExecuteEditorCommandAsync("output"); outputTabs.SelectedIndex = 0; await VerifyDockingAsync("output-restored");
await CaptureWindowAsync(Path.Combine(output, "workflow-editor-connected-tabs-dark.png"));
root.RequestedTheme = ElementTheme.Light; await VerifyDockingAsync("light");
await CaptureWindowAsync(Path.Combine(output, "workflow-editor-connected-tabs-light.png"));
if (colors[ElementTheme.Dark] == colors[ElementTheme.Light]) throw new InvalidOperationException("工作流编辑区没有适配深浅主题");
await Infrastructure.Storage.AtomicJson.WriteAsync(Path.Combine(output, "workflow-document-docking.json"), dockingEvidence);
root.RequestedTheme = originalTheme;
var search = Descendants<TextBox>(hub).Single(x => x.Name == "PaletteSearch"); search.Text = "输出日志";
await WaitManagementAsync(() => a.ViewModel.PaletteRows.OfType<PaletteItem>().Any(x => x.Descriptor.Id == "core.log"));
var paletteList = Descendants<ListView>(hub).Single(x => x.Name == "PaletteList"); var logItem = a.ViewModel.PaletteRows.OfType<PaletteItem>().Single(x => x.Descriptor.Id == "core.log");
paletteList.ScrollIntoView(logItem); await WaitManagementAsync(() => paletteList.ContainerFromItem(logItem) is FrameworkElement);
var nodeCount = a.ViewModel.SelectedWorkflow!.Graph.Nodes.Count;
var container = (ListViewItem)paletteList.ContainerFromItem(logItem);
var peer = FrameworkElementAutomationPeer.FromElement(container) ?? new ListViewItemAutomationPeer(container);
if (peer.GetPattern(PatternInterface.Invoke) is not IInvokeProvider invoke) throw new InvalidOperationException("节点库项缺少自动化调用入口");
invoke.Invoke();
await WaitManagementAsync(() => a.ViewModel.SelectedWorkflow!.Graph.Nodes.Count == nodeCount + 1);
await a.ExecuteEditorCommandAsync("undo"); if (a.ViewModel.SelectedWorkflow!.Graph.Nodes.Count != nodeCount) throw new InvalidOperationException("独立节点库添加与撤销未作用到当前工作流"); search.Text = "";
await services.Workflows.SetActivationAsync(first.Id, true, 9); await hub.SaveAllAsync();
if ((await services.Workflows.GetAsync(first.Id)).Enabled != true) throw new InvalidOperationException("编辑器保存覆盖了列表的启停状态");
await CaptureWindowAsync(Path.Combine(output, "workflow-editor-multiple-tabs-menus.png"));
navigation.NavigateTo<WorkflowListPage>(); await Task.Delay(300);
var frame = Descendants<Frame>(App.MainWindow.Content).Single(x => x.Content is WorkflowListPage); var list = (WorkflowListPage)frame.Content;
await list.SetEnabledAsync(first.Id, false);
if (services.Scheduler.StatusFor(await services.Workflows.GetAsync(first.Id)).Any(x => x.Enabled)) throw new InvalidOperationException("列表关闭后仍在调度");
navigation.NavigateTo<WorkflowEditorPage>(); await Task.Delay(150); if (hub.Documents.TabItems.Count != 2) throw new InvalidOperationException("离开编辑器后标签页丢失");
}
finally
{
await WorkflowEditorPage.CloseOpenAsync(first.Id); await WorkflowEditorPage.CloseOpenAsync(second.Id); navigation.NavigateTo<MainPage>();
services.State.SetPaneSize("workflow.palette", paletteSize); services.State.SetPaneSize("workflow.output", outputSize); await services.State.SaveAsync(); root.RequestedTheme = originalTheme; App.MainWindow.AppWindow.Resize(originalSize);
}
}
}