Files
XplorePlane/XplorePlane.Tests/Pipeline/PipelineEditorViewModelTests.cs
T
2026-04-20 09:37:28 +08:00

292 lines
10 KiB
C#

using Moq;
using XP.Common.Logging.Interfaces;
using XplorePlane.Models;
using XplorePlane.Services;
using XplorePlane.Tests.Helpers;
using XplorePlane.ViewModels;
using Xunit;
namespace XplorePlane.Tests.Pipeline
{
/// <summary>
/// 测试 PipelineEditorViewModel 的命令逻辑
/// 覆盖任务 6.1 / 6.2 / 6.3 / 6.4 / 6.8
/// </summary>
public class PipelineEditorViewModelTests
{
private readonly Mock<IImageProcessingService> _mockImageSvc;
private readonly Mock<IPipelineExecutionService> _mockExecSvc;
private readonly Mock<IPipelinePersistenceService> _mockPersistSvc;
private readonly Mock<ILoggerService> _mockLogger;
public PipelineEditorViewModelTests()
{
_mockImageSvc = TestHelpers.CreateMockImageService();
_mockExecSvc = new Mock<IPipelineExecutionService>();
_mockPersistSvc = new Mock<IPipelinePersistenceService>();
_mockLogger = new Mock<ILoggerService>();
_mockLogger.Setup(l => l.ForModule<PipelineEditorViewModel>()).Returns(_mockLogger.Object);
}
private PipelineEditorViewModel CreateVm() =>
new PipelineEditorViewModel(_mockImageSvc.Object, _mockExecSvc.Object, _mockPersistSvc.Object, _mockLogger.Object);
// ── 6.1 AddOperatorCommand ────────────────────────────────────
[Fact]
public void AddOperator_ValidKey_AppendsNode()
{
var vm = CreateVm();
vm.AddOperatorCommand.Execute("Blur");
Assert.Single(vm.PipelineNodes);
Assert.Equal("Blur", vm.PipelineNodes[0].OperatorKey);
Assert.Equal(0, vm.PipelineNodes[0].Order);
}
[Fact]
public void AddOperator_KnownKey_SetsIconPath()
{
_mockImageSvc.Setup(s => s.GetAvailableProcessors()).Returns(new[] { "ShockFilter" });
var vm = CreateVm();
vm.AddOperatorCommand.Execute("ShockFilter");
Assert.Single(vm.PipelineNodes);
Assert.Equal("⚡", vm.PipelineNodes[0].IconPath);
}
[Fact]
public void AddOperator_InvalidKey_NodeNotAdded()
{
var vm = CreateVm();
vm.AddOperatorCommand.Execute("NonExistentKey");
Assert.Empty(vm.PipelineNodes);
Assert.Contains("未注册", vm.StatusMessage);
}
[Fact]
public void AddOperator_NullKey_NodeNotAdded()
{
var vm = CreateVm();
vm.AddOperatorCommand.Execute(null);
Assert.Empty(vm.PipelineNodes);
}
[Fact]
public void AddOperator_ExceedsMaxLength_NodeNotAdded()
{
// 主项目中 MaxPipelineLength = 20,通过反射读取或直接添加 20 个
var vm = CreateVm();
for (int i = 0; i < 20; i++)
vm.AddOperatorCommand.Execute("Blur");
Assert.Equal(20, vm.PipelineNodes.Count);
vm.AddOperatorCommand.Execute("Blur");
Assert.Equal(20, vm.PipelineNodes.Count);
Assert.Contains("上限", vm.StatusMessage);
}
[Fact]
public void AddOperator_MultipleNodes_OrderIsSequential()
{
var vm = CreateVm();
vm.AddOperatorCommand.Execute("Blur");
vm.AddOperatorCommand.Execute("Sharpen");
vm.AddOperatorCommand.Execute("Threshold");
Assert.Equal(3, vm.PipelineNodes.Count);
for (int i = 0; i < 3; i++)
Assert.Equal(i, vm.PipelineNodes[i].Order);
}
// ── 6.2 RemoveOperatorCommand ─────────────────────────────────
[Fact]
public void RemoveOperator_ExistingNode_NodeRemoved()
{
var vm = CreateVm();
vm.AddOperatorCommand.Execute("Blur");
var node = vm.PipelineNodes[0];
vm.RemoveOperatorCommand.Execute(node);
Assert.Empty(vm.PipelineNodes);
}
[Fact]
public void RemoveOperator_MiddleNode_OrderRenumbered()
{
var vm = CreateVm();
vm.AddOperatorCommand.Execute("Blur");
vm.AddOperatorCommand.Execute("Sharpen");
vm.AddOperatorCommand.Execute("Threshold");
var middle = vm.PipelineNodes[1];
vm.RemoveOperatorCommand.Execute(middle);
Assert.Equal(2, vm.PipelineNodes.Count);
Assert.Equal(0, vm.PipelineNodes[0].Order);
Assert.Equal(1, vm.PipelineNodes[1].Order);
}
[Fact]
public void RemoveOperator_SelectedNode_SelectedNodeCleared()
{
var vm = CreateVm();
vm.AddOperatorCommand.Execute("Blur");
var node = vm.PipelineNodes[0];
vm.SelectedNode = node;
vm.RemoveOperatorCommand.Execute(node);
Assert.Null(vm.SelectedNode);
}
[Fact]
public void RemoveOperator_NullNode_NoException()
{
var vm = CreateVm();
var ex = Record.Exception(() => vm.RemoveOperatorCommand.Execute(null));
Assert.Null(ex);
}
// ── 6.3 ReorderOperatorCommand ────────────────────────────────
[Fact]
public void ReorderOperator_MoveFirstToLast_OrderConsistent()
{
var vm = CreateVm();
vm.AddOperatorCommand.Execute("Blur");
vm.AddOperatorCommand.Execute("Sharpen");
vm.AddOperatorCommand.Execute("Threshold");
vm.ReorderOperatorCommand.Execute(new PipelineReorderArgs { OldIndex = 0, NewIndex = 2 });
for (int i = 0; i < vm.PipelineNodes.Count; i++)
Assert.Equal(i, vm.PipelineNodes[i].Order);
}
[Fact]
public void ReorderOperator_MoveLastToFirst_OrderConsistent()
{
var vm = CreateVm();
vm.AddOperatorCommand.Execute("Blur");
vm.AddOperatorCommand.Execute("Sharpen");
vm.AddOperatorCommand.Execute("Threshold");
var lastName = vm.PipelineNodes[2].OperatorKey;
vm.ReorderOperatorCommand.Execute(new PipelineReorderArgs { OldIndex = 2, NewIndex = 0 });
Assert.Equal(lastName, vm.PipelineNodes[0].OperatorKey);
for (int i = 0; i < vm.PipelineNodes.Count; i++)
Assert.Equal(i, vm.PipelineNodes[i].Order);
}
[Fact]
public void ReorderOperator_SameIndex_NoChange()
{
var vm = CreateVm();
vm.AddOperatorCommand.Execute("Blur");
vm.AddOperatorCommand.Execute("Sharpen");
var originalFirst = vm.PipelineNodes[0].OperatorKey;
vm.ReorderOperatorCommand.Execute(new PipelineReorderArgs { OldIndex = 0, NewIndex = 0 });
Assert.Equal(originalFirst, vm.PipelineNodes[0].OperatorKey);
}
[Fact]
public void ReorderOperator_OutOfBoundsIndex_NoException()
{
var vm = CreateVm();
vm.AddOperatorCommand.Execute("Blur");
var ex = Record.Exception(() =>
vm.ReorderOperatorCommand.Execute(new PipelineReorderArgs { OldIndex = 0, NewIndex = 99 }));
Assert.Null(ex);
}
// ── 6.4 LoadNodeParameters ────────────────────────────────────
[Fact]
public void LoadNodeParameters_ModifiedValuePreserved()
{
var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 20);
_mockImageSvc.Setup(s => s.GetProcessorParameters("Blur"))
.Returns(new[] { param });
var vm = CreateVm();
vm.AddOperatorCommand.Execute("Blur");
var node = vm.PipelineNodes[0];
// 修改参数值
node.Parameters[0].Value = 10;
// 重新选中节点触发 LoadNodeParameters
vm.SelectedNode = null;
vm.SelectedNode = node;
Assert.Equal(10, node.Parameters[0].Value);
}
[Fact]
public void LoadNodeParameters_UnmodifiedUsesDefault()
{
var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 20);
_mockImageSvc.Setup(s => s.GetProcessorParameters("Blur"))
.Returns(new[] { param });
var vm = CreateVm();
vm.AddOperatorCommand.Execute("Blur");
var node = vm.PipelineNodes[0];
Assert.Equal(5, node.Parameters[0].Value);
}
// ── 6.8 参数范围校验 ──────────────────────────────────────────
[Fact]
public void ParameterValidation_ValueWithinRange_IsValid()
{
var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 10);
var vm = new ProcessorParameterVM(param) { Value = 7 };
Assert.True(vm.IsValueValid);
}
[Fact]
public void ParameterValidation_ValueBelowMin_IsInvalid()
{
var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 10);
var vm = new ProcessorParameterVM(param) { Value = 0 };
Assert.False(vm.IsValueValid);
}
[Fact]
public void ParameterValidation_ValueAboveMax_IsInvalid()
{
var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 10);
var vm = new ProcessorParameterVM(param) { Value = 11 };
Assert.False(vm.IsValueValid);
}
[Fact]
public void ParameterValidation_BoundaryValues_AreValid()
{
var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 10);
var vmMin = new ProcessorParameterVM(param) { Value = 1 };
var vmMax = new ProcessorParameterVM(param) { Value = 10 };
Assert.True(vmMin.IsValueValid);
Assert.True(vmMax.IsValueValid);
}
}
}