Files
personal-toolbox/PersonalToolBox.Tests/ViewModels/GroupEditViewModelTests.cs
home-PC 85919381b1 Phase 7: 自定义图标系统 + 组合嵌套 + 删除功能 + UI 优化
- 安装 FontAwesome.Sharp v6.6.0,新增 Helpers/IconProvider.cs(190+ 图标,11 分类)
- ToolEditWindow/GroupEditWindow: 添加图标选择 ComboBox,按分类分组,带图标预览
- MainWindow 卡片模板: 使用 fa:IconBlock 渲染图标,IconCode 为空时回退首字母
- 组合角标从 emoji 改为 FontAwesome Cubes 图标,放置于卡片右上角
- ComboBox 样式修复: TextBlock→ContentPresenter,支持 ItemTemplate+DisplayMemberPath
- ComboBox 滚轮修复: CanContentScroll=False 解决分组模式下滑轮跳组问题
- 编辑弹窗: 所有输入控件添加 VerticalContentAlignment=Center
- 组合可包含其他组合: GetAncestorIds 递归排除自身及祖先防止循环引用
- ProcessExecutionService: 支持嵌套组合递归执行,visited 集合防死循环
- MainWindow 右键菜单新增删除功能,工具和组合均支持
- 移除侧边栏顶部个人工具箱标题文字
- 更新测试: 适配组合嵌套逻辑,新增祖先排除测试 (83/83 通过)
2026-05-10 02:19:32 +08:00

118 lines
3.8 KiB
C#

using Moq;
using PersonalToolBox.Models;
using PersonalToolBox.Services;
using PersonalToolBox.ViewModels;
namespace PersonalToolBox.Tests.ViewModels;
public class GroupEditViewModelTests
{
private readonly Mock<IDataService> _dataServiceMock = new();
private readonly Mock<ILogService> _logServiceMock = new();
private readonly AppConfig _config;
public GroupEditViewModelTests()
{
_config = new AppConfig
{
Tools =
[
new() { Id = "t1", Name = "VS Code", IsValid = true },
new() { Id = "t2", Name = "Postman", IsValid = true },
new() { Id = "t3", Name = "SomeGroup", IsGroup = true, SubToolIds = new List<string> { "t1" }, IsValid = true }
]
};
_dataServiceMock.Setup(d => d.Config).Returns(_config);
}
[Fact]
public void Constructor_AddMode_LoadsAllToolsIncludingGroups()
{
var vm = new GroupEditViewModel(_dataServiceMock.Object, _logServiceMock.Object);
Assert.Equal("添加组合", vm.WindowTitle);
Assert.Equal(3, vm.AvailableTools.Count); // t1, t2, t3 (new groups can include other groups)
}
[Fact]
public void Constructor_EditMode_ExcludesSelfAndAncestors()
{
// Group A (t3) contains t1 -> Group B being edited should exclude itself
var group = new ToolItem
{
Id = "t4",
Name = "GroupB",
IsGroup = true,
SubToolIds = new List<string>()
};
// t3 contains t4, so t3 is an ancestor of t4
_config.Tools.First(t => t.Id == "t3").SubToolIds.Add("t4");
var vm = new GroupEditViewModel(_dataServiceMock.Object, _logServiceMock.Object, group);
// t4 (self) and t3 (ancestor) should be excluded
Assert.Equal(2, vm.AvailableTools.Count); // t1, t2 only
}
[Fact]
public void Constructor_EditMode_PreselectedTools()
{
var group = new ToolItem
{
Name = "ExistingGroup",
IsGroup = true,
SubToolIds = new List<string> { "t1" }
};
var vm = new GroupEditViewModel(_dataServiceMock.Object, _logServiceMock.Object, group);
Assert.Equal("编辑组合", vm.WindowTitle);
Assert.Equal("ExistingGroup", vm.Name);
Assert.True(vm.AvailableTools.First(t => t.Tool.Id == "t1").IsSelected);
Assert.False(vm.AvailableTools.First(t => t.Tool.Id == "t2").IsSelected);
}
[Fact]
public void Save_EmptyName_LogsWarning()
{
var vm = new GroupEditViewModel(_dataServiceMock.Object, _logServiceMock.Object);
vm.SaveCommand.Execute(null);
_logServiceMock.Verify(x => x.Warning(It.Is<string>(s => s.Contains("名称不能为空"))), Times.Once);
}
[Fact]
public void Save_AddMode_CreatesGroupAndCloses()
{
var vm = new GroupEditViewModel(_dataServiceMock.Object, _logServiceMock.Object)
{
Name = "NewGroup"
};
vm.AvailableTools.First(t => t.Tool.Id == "t1").IsSelected = true;
bool? closeResult = null;
vm.CloseAction = (r) => closeResult = r;
vm.SaveCommand.Execute(null);
Assert.True(vm.Saved);
Assert.True(closeResult);
Assert.Single(_config.Tools.Where(t => t.IsGroup && t.Name == "NewGroup"));
Assert.Contains("t1", _config.Tools.First(t => t.Name == "NewGroup").SubToolIds);
_dataServiceMock.Verify(x => x.Save(), Times.Once);
}
[Fact]
public void Cancel_ClosesWithoutSaving()
{
var vm = new GroupEditViewModel(_dataServiceMock.Object, _logServiceMock.Object);
bool? closeResult = null;
vm.CloseAction = (r) => closeResult = r;
vm.CancelCommand.Execute(null);
Assert.False(vm.Saved);
Assert.False(closeResult);
}
}