- 数据模型: ToolItem 新增 IsGroup(bool) + SubToolIds(List<string>) 字段
- 执行逻辑: ProcessExecutionService 改为 ExecuteAsync, 组合卡片遍历子工具逐一启动(500ms延迟), 孤儿ID跳过并打印警告
- 组合编辑: GroupEditViewModel + GroupEditWindow, 复选框列表勾选非组合工具
- 主界面: 标题栏新增 '+添加组合' 按钮(蓝色), 组合卡片右下角显示 📦 角标
- 右键菜单: 区分 '编辑工具' (普通) 和 '编辑组合' (IsGroup=true)
- 快捷键: HotKeyManager 适配 ExecuteAsync 异步调用
- 测试: 82 tests total (ProcessExecution 4->6, GroupEdit 5 new)
191 lines
5.5 KiB
C#
191 lines
5.5 KiB
C#
using Moq;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using PersonalToolBox.Helpers;
|
|
using PersonalToolBox.Models;
|
|
using PersonalToolBox.Services;
|
|
using PersonalToolBox.ViewModels;
|
|
|
|
namespace PersonalToolBox.Tests.ViewModels;
|
|
|
|
public class MainViewModelTests
|
|
{
|
|
private readonly Mock<ILogService> _logServiceMock = new();
|
|
private readonly Mock<IDataService> _dataServiceMock = new();
|
|
private readonly Mock<IProcessExecutionService> _processServiceMock = new();
|
|
private readonly Mock<IServiceProvider> _serviceProviderMock = new();
|
|
private readonly Mock<HotKeyManager> _hotKeyManagerMock;
|
|
private readonly AppConfig _config;
|
|
|
|
public MainViewModelTests()
|
|
{
|
|
_config = new AppConfig
|
|
{
|
|
Theme = "Dark",
|
|
Categories =
|
|
[
|
|
new() { Id = "1", Name = "开发工具" },
|
|
new() { Id = "2", Name = "系统工具" }
|
|
],
|
|
Tools =
|
|
[
|
|
new() { Id = "t1", Name = "VS Code", CategoryId = "1", IsValid = true },
|
|
new() { Id = "t2", Name = "Postman", CategoryId = "1", IsValid = true },
|
|
new() { Id = "t3", Name = "任务管理器", CategoryId = "2", IsValid = true },
|
|
new() { Id = "t4", Name = "失效工具", CategoryId = "1", ExecutablePath = @"C:\missing.exe", IsValid = false }
|
|
]
|
|
};
|
|
|
|
_hotKeyManagerMock = new Mock<HotKeyManager>(_logServiceMock.Object, _processServiceMock.Object);
|
|
_dataServiceMock.Setup(d => d.Config).Returns(_config);
|
|
}
|
|
|
|
private MainViewModel CreateViewModel() =>
|
|
new(_logServiceMock.Object, _dataServiceMock.Object, _processServiceMock.Object,
|
|
_serviceProviderMock.Object, _hotKeyManagerMock.Object);
|
|
|
|
[Fact]
|
|
public void Constructor_LoadsCategories_IncludingAll()
|
|
{
|
|
var vm = CreateViewModel();
|
|
|
|
Assert.Equal(3, vm.Categories.Count);
|
|
Assert.Equal("全部", vm.Categories[0].Name);
|
|
Assert.Equal("开发工具", vm.Categories[1].Name);
|
|
Assert.Equal("系统工具", vm.Categories[2].Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_LoadsAllTools()
|
|
{
|
|
var vm = CreateViewModel();
|
|
Assert.Equal(4, vm.Tools.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Default_CategoryIsAll_ShowsAllTools()
|
|
{
|
|
var vm = CreateViewModel();
|
|
Assert.Equal(4, vm.FilteredTools.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectCategory_FiltersByCategory()
|
|
{
|
|
var vm = CreateViewModel();
|
|
vm.SelectedCategory = vm.Categories[1];
|
|
Assert.Equal(3, vm.FilteredTools.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void SearchText_FiltersByName()
|
|
{
|
|
var vm = CreateViewModel();
|
|
vm.SearchText = "post";
|
|
Assert.Single(vm.FilteredTools);
|
|
Assert.Equal("Postman", vm.FilteredTools[0].Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void SearchText_CaseInsensitive()
|
|
{
|
|
var vm = CreateViewModel();
|
|
vm.SearchText = "POSTMAN";
|
|
Assert.Single(vm.FilteredTools);
|
|
Assert.Equal("Postman", vm.FilteredTools[0].Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void SearchText_And_Category_FilterCombined()
|
|
{
|
|
var vm = CreateViewModel();
|
|
vm.SelectedCategory = vm.Categories[1];
|
|
vm.SearchText = "code";
|
|
Assert.Single(vm.FilteredTools);
|
|
Assert.Equal("VS Code", vm.FilteredTools[0].Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void SearchText_Empty_ShowsAllInCategory()
|
|
{
|
|
var vm = CreateViewModel();
|
|
vm.SelectedCategory = vm.Categories[2];
|
|
vm.SearchText = "";
|
|
Assert.Single(vm.FilteredTools);
|
|
Assert.Equal("任务管理器", vm.FilteredTools[0].Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void SearchText_NoMatch_ReturnsEmpty()
|
|
{
|
|
var vm = CreateViewModel();
|
|
vm.SearchText = "nonexistent";
|
|
Assert.Empty(vm.FilteredTools);
|
|
}
|
|
|
|
[Fact]
|
|
public void InvalidTools_StillAppearInFilteredResults()
|
|
{
|
|
var vm = CreateViewModel();
|
|
vm.SelectedCategory = vm.Categories[1];
|
|
var invalid = vm.FilteredTools.FirstOrDefault(t => t.Name == "失效工具");
|
|
Assert.NotNull(invalid);
|
|
Assert.False(invalid.IsValid);
|
|
}
|
|
|
|
[Fact]
|
|
public void ClearLogsCommand_CallsLogServiceClear()
|
|
{
|
|
var vm = CreateViewModel();
|
|
vm.ClearLogsCommand.Execute(null);
|
|
_logServiceMock.Verify(x => x.Clear(), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToggleTheme_SwitchesDarkToLight()
|
|
{
|
|
var vm = CreateViewModel();
|
|
Assert.Equal("Dark", vm.CurrentTheme);
|
|
vm.ToggleThemeCommand.Execute(null);
|
|
Assert.Equal("Light", vm.CurrentTheme);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToggleTheme_CallsSave()
|
|
{
|
|
var vm = CreateViewModel();
|
|
vm.ToggleThemeCommand.Execute(null);
|
|
_dataServiceMock.Verify(x => x.Save(), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExecuteTool_CallsProcessService()
|
|
{
|
|
var vm = CreateViewModel();
|
|
var tool = vm.Tools[0];
|
|
|
|
vm.ExecuteToolCommand.Execute(tool);
|
|
|
|
_processServiceMock.Verify(x => x.ExecuteAsync(tool), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExecuteTool_Null_DoesNotCallProcessService()
|
|
{
|
|
var vm = CreateViewModel();
|
|
vm.ExecuteToolCommand.Execute(null);
|
|
_processServiceMock.Verify(x => x.ExecuteAsync(It.IsAny<ToolItem>()), Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public void RefreshData_ReloadsFromConfig()
|
|
{
|
|
var vm = CreateViewModel();
|
|
|
|
_config.Tools.Add(new ToolItem { Name = "NewTool" });
|
|
|
|
vm.RefreshData();
|
|
|
|
Assert.Equal(5, vm.Tools.Count);
|
|
}
|
|
}
|