Files
XplorePlane/XplorePlane.Tests/ViewModels/MatrixEditorViewModelTests.cs
T
zhengxuan.zhang e4f1cc2e05 refactor: 迁移剩余对话框到 IApplicationDialogService,拆分 MatrixEditorViewModel 与 MainViewModel
对话框迁移(全部采用可选构造参数 dialogs,保持向后兼容):
- SettingsViewModel:9 处 MessageBox + 4 处文件/文件夹对话框
- InspectionMapViewModel:4 处 HexMessageBox + 3 处 MessageBox(含三态确认)+ 1 处 OpenFileDialog
- NavigationPropertyPanelViewModel:SaveFileDialog/OpenFileDialog/MessageBox(导航图保存、标定加载)
- Debug 目录(SnapshotManagerViewModel/EventLogViewModel/SnapshotDiffViewModel):全部 MessageBox 与文件对话框
- IApplicationDialogService 新增 ConfirmThreeWay 方法及 SelectOpenFile/SelectSaveFile 可选 initialDirectory 参数

God Class 拆分:
- 从 MatrixEditorViewModel 提取 MatrixOffsetCalculator(推荐示教点计算、自动生成偏移算法,纯静态计算无 UI 依赖)
  和 MatrixExecutionCoordinator(模板程序加载与执行编排,替代内联 try-catch)
- 从 MainViewModel 提取 WindowLauncherService,统一管理原 18+ 个 private Window 单例字段,
  提供 ShowOrActivate/ShowOrActivateVisible/Show/CloseIfOpen 四种窗口生命周期模式

文档:
- 修正 doc/代码评审.md 第二、三阶段标题状态矛盾及不准确的全称断言,新增第四阶段跟踪剩余工作

资源文件:
- Resources.resx(默认/zh-CN/zh-TW/en-US)新增约 100+ 本地化键,覆盖上述对话框迁移涉及的消息、标题与文件过滤器

测试:
- 同步更新 PipelineEditorViewModelTests/PipelinePropertyTests/CncEditorViewModelTests/MatrixEditorViewModelTests 的构造签名
- 全量 386 个测试通过,无回归
2026-07-17 13:04:11 +08:00

167 lines
6.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using Prism.Events;
using XP.Common.Logging.Interfaces;
using XP.Hardware.MotionControl.Abstractions;
using XP.Hardware.MotionControl.Abstractions.Enums;
using Xunit;
using XplorePlane.Models;
using XplorePlane.Services.Cnc;
using XplorePlane.Services.Cnc.Matrix;
using XplorePlane.Services.Dialogs;
using XplorePlane.ViewModels.Cnc;
namespace XplorePlane.Tests.ViewModels
{
public class MatrixEditorViewModelTests
{
[Fact]
public void Constructor_CreatesTwoByTwoLayoutByDefault()
{
var fixture = CreateFixture();
Assert.Equal(2, fixture.ViewModel.Rows);
Assert.Equal(2, fixture.ViewModel.Columns);
Assert.Equal(4, fixture.ViewModel.Cells.Count);
Assert.Equal(new[] { 1, 4 }, fixture.ViewModel.TeachPoints.Select(point => point.Position));
}
[Fact]
public void RecordCurrentPosition_RefreshesMotionAxesAndRecordsLatestCoordinates()
{
var fixture = CreateFixture(stageXmm: 12.3456, stageYmm: -3.2546);
var teachPoint = fixture.ViewModel.CurrentTeachPoint;
fixture.ViewModel.RecordCurrentPositionCommand.Execute();
fixture.StageX.Verify(axis => axis.UpdateStatus(), Times.Once);
fixture.StageY.Verify(axis => axis.UpdateStatus(), Times.Once);
Assert.True(teachPoint.HasRecorded);
Assert.Equal(12.346, teachPoint.RecordedX);
Assert.Equal(-3.255, teachPoint.RecordedY);
Assert.Equal("X:12.346 Y:-3.255 mm", teachPoint.CoordinateLabel);
}
[Fact]
public void AutoGenerate_UsesRecordedMillimeterCoordinatesWithoutUnitScaling()
{
var fixture = CreateFixture(stageXmm: 10.1234, stageYmm: 20.1234);
fixture.ViewModel.RecordCurrentPositionCommand.Execute();
fixture.StageX.SetupGet(axis => axis.ActualPosition).Returns(11.2345);
fixture.StageY.SetupGet(axis => axis.ActualPosition).Returns(25.6789);
fixture.ViewModel.RecordCurrentPositionCommand.Execute();
fixture.ViewModel.AutoGenerateCommand.Execute();
var topRight = fixture.ViewModel.Cells[1];
var bottomLeft = fixture.ViewModel.Cells[2];
var bottomRight = fixture.ViewModel.Cells[3];
Assert.Equal(1.112, topRight.OffsetX);
Assert.Equal(0, topRight.OffsetY);
Assert.Equal(0, bottomLeft.OffsetX);
Assert.Equal(5.556, bottomLeft.OffsetY);
Assert.Equal(1.112, bottomRight.OffsetX);
Assert.Equal(5.556, bottomRight.OffsetY);
Assert.Equal("X:10.123 Y:25.679", bottomLeft.CoordinateLabel);
Assert.Equal("X:11.235 Y:25.679", bottomRight.CoordinateLabel);
}
[Fact]
public void CellCommands_RecordAdditionalPositionAndToggleEnabledState()
{
var fixture = CreateFixture(stageXmm: 7.1234, stageYmm: 8.5678);
var targetCell = fixture.ViewModel.Cells[1];
fixture.ViewModel.RecordCellPositionCommand.Execute(targetCell);
var additionalPoint = Assert.Single(
fixture.ViewModel.TeachPoints,
point => point.Position == 2);
Assert.True(additionalPoint.IsTaught);
Assert.Equal(7.123, additionalPoint.RecordedX);
Assert.Equal(8.568, additionalPoint.RecordedY);
Assert.Equal(MatrixCellTeachState.Taught, fixture.ViewModel.Cells[1].TeachState);
fixture.ViewModel.ToggleCellCommand.Execute(fixture.ViewModel.Cells[1]);
Assert.False(fixture.ViewModel.Cells[1].IsEnabled);
}
private static Fixture CreateFixture(double stageXmm = 0, double stageYmm = 0)
{
var matrixService = new Mock<IMatrixService>();
matrixService
.Setup(service => service.CreateLayout(
It.IsAny<int>(), It.IsAny<int>(), It.IsAny<double>(), It.IsAny<double>()))
.Returns<int, int, double, double>(CreateLayout);
matrixService
.Setup(service => service.ToggleCellEnabled(
It.IsAny<MatrixLayout>(), It.IsAny<int>(), It.IsAny<int>()))
.Returns<MatrixLayout, int, int>((layout, row, column) =>
{
var cells = layout.Cells
.Select(cell => cell.Row == row && cell.Column == column
? cell with { IsEnabled = !cell.IsEnabled }
: cell)
.ToList()
.AsReadOnly();
return layout with { Cells = cells };
});
var stageX = new Mock<ILinearAxis>();
stageX.SetupGet(axis => axis.ActualPosition).Returns(stageXmm);
var stageY = new Mock<ILinearAxis>();
stageY.SetupGet(axis => axis.ActualPosition).Returns(stageYmm);
var motionSystem = new Mock<IMotionSystem>();
motionSystem.Setup(system => system.GetLinearAxis(AxisId.StageX)).Returns(stageX.Object);
motionSystem.Setup(system => system.GetLinearAxis(AxisId.StageY)).Returns(stageY.Object);
var logger = new Mock<ILoggerService>();
logger.Setup(service => service.ForModule<MatrixEditorViewModel>()).Returns(logger.Object);
var viewModel = new MatrixEditorViewModel(
matrixService.Object,
Mock.Of<ICncProgramService>(),
Mock.Of<IMatrixExecutionCoordinator>(),
motionSystem.Object,
Mock.Of<IEventAggregator>(),
logger.Object,
Mock.Of<IApplicationDialogService>());
return new Fixture(viewModel, stageX, stageY);
}
private static MatrixLayout CreateLayout(int rows, int columns, double rowSpacing, double columnSpacing)
{
var cells = new List<MatrixCell>();
for (var row = 0; row < rows; row++)
{
for (var column = 0; column < columns; column++)
{
cells.Add(new MatrixCell(
row,
column,
column * columnSpacing,
row * rowSpacing,
true,
MatrixCellStatus.NotExecuted,
null));
}
}
return new MatrixLayout(
Guid.NewGuid(), rows, columns, rowSpacing, columnSpacing,
0, 0, string.Empty, cells.AsReadOnly());
}
private sealed record Fixture(
MatrixEditorViewModel ViewModel,
Mock<ILinearAxis> StageX,
Mock<ILinearAxis> StageY);
}
}