docs: plan AI test plugin UI optimization
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
# AI 单测插件完整 UI 优化实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 将 AI 单测插件改为四个主 Tab 的低密度工作台,并提供目录选择、准确的模块反馈和只在主动任务期间出现的全局进度。
|
||||
|
||||
**Architecture:** `AiTestPane` 保留工作流编排,但把可独立验证的行为下沉到 `DirectoryPathSelector`、`TaskProgressState` 和现有 `StartModuleSelector`。项目加载继续使用 `LatestRequestGate`,Git 分析继续传递显式 `baseline` 与 `targetRef`。
|
||||
|
||||
**Tech Stack:** Java 17、JavaFX 17、Spring Boot 2.7、Maven、JUnit 5
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 主页面固定为 `项目与增量`、`工具链`、`质量规范`、`结果与合并` 四个不可关闭 Tab,不再使用 Accordion。
|
||||
- JDK 8 Home 与 Maven Home 必须通过目录选择器选择,路径框只读。
|
||||
- 基线默认 `master`,目标默认 `HEAD`;不得破坏任意 `baseline...targetRef` 分析能力。
|
||||
- 项目加载不得进入全局 busy,较早异步结果不得覆盖最新选择。
|
||||
- 空闲时全局进度条隐藏并重置;仅用户主动的分析、生成、验证、合并和工具链检查显示。
|
||||
- 真实 `ft-loancapital-core` 必须继续检出 `ft-loancapital-core-start`。
|
||||
- 不触碰现有未跟踪的审计文档、截图目录和 SQL 文件。
|
||||
- 全程单线程实施,不使用子代理。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 可测试的目录选择组件
|
||||
|
||||
**Files:**
|
||||
- Create: `src/main/java/com/example/workutils/biz/aitest/ui/DirectoryPathSelector.java`
|
||||
- Test: `src/test/java/com/example/workutils/biz/aitest/ui/DirectoryPathSelectorTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `DirectoryPathSelector(String label, Supplier<Window> ownerSupplier)`
|
||||
- Produces: `Path getPath()`、`void setPath(Path)`、`TextField pathField()`、`Button chooseButton()`
|
||||
- Internal test seam: package-private `void applySelection(File selected)`,`null` 表示取消。
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
```java
|
||||
@Test
|
||||
void pathIsReadOnlyAndCancelKeepsExistingValue() throws Exception {
|
||||
DirectoryPathSelector selector = FxTestSupport.fx(
|
||||
() -> new DirectoryPathSelector("JDK 8 Home", () -> null));
|
||||
FxTestSupport.fx(() -> selector.setPath(Path.of("C:/jdk8")));
|
||||
FxTestSupport.fx(() -> selector.applySelection(null));
|
||||
assertFalse(FxTestSupport.fx(() -> selector.pathField().isEditable()));
|
||||
assertEquals(Path.of("C:/jdk8"), FxTestSupport.fx(selector::getPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectedDirectoryUpdatesValue() throws Exception {
|
||||
DirectoryPathSelector selector = FxTestSupport.fx(
|
||||
() -> new DirectoryPathSelector("Maven Home", () -> null));
|
||||
FxTestSupport.fx(() -> selector.applySelection(new File("C:/maven")));
|
||||
assertEquals(Path.of("C:/maven"), FxTestSupport.fx(selector::getPath));
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行 RED**
|
||||
|
||||
Run: `mvn -Dtest=DirectoryPathSelectorTest test`
|
||||
|
||||
Expected: FAIL,因为 `DirectoryPathSelector` 尚不存在。
|
||||
|
||||
- [ ] **Step 3: 实现组件**
|
||||
|
||||
组件继承 `HBox`,内部包含只读 `TextField` 与文字按钮“选择目录”。按钮创建 `DirectoryChooser`;初始目录仅在当前路径存在且为目录时设置。`applySelection(null)` 不改变路径。
|
||||
|
||||
- [ ] **Step 4: 运行 GREEN**
|
||||
|
||||
Run: `mvn -Dtest=DirectoryPathSelectorTest test`
|
||||
|
||||
Expected: 2 tests,0 failures。
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add src/main/java/com/example/workutils/biz/aitest/ui/DirectoryPathSelector.java src/test/java/com/example/workutils/biz/aitest/ui/DirectoryPathSelectorTest.java
|
||||
git commit -m "feat: add directory selectors for AI test toolchain"
|
||||
```
|
||||
|
||||
### Task 2: 进度状态与模块展示策略
|
||||
|
||||
**Files:**
|
||||
- Create: `src/main/java/com/example/workutils/biz/aitest/ui/TaskProgressState.java`
|
||||
- Create: `src/main/java/com/example/workutils/biz/aitest/project/ModuleLoadPresentation.java`
|
||||
- Test: `src/test/java/com/example/workutils/biz/aitest/ui/TaskProgressStateTest.java`
|
||||
- Test: `src/test/java/com/example/workutils/biz/aitest/project/ModuleLoadPresentationTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `TaskProgressState.start()`、`update(double)`、`finish()`,以及 `visible()`、`progress()`。
|
||||
- Consumes: `StartModuleSelector.autoSelect(MavenProjectModel)`。
|
||||
- Produces: `ModuleLoadPresentation.from(MavenProjectModel, String preferredArtifactId)`,返回 `moduleCount()`、`selectedModule()` 与 `message()`。
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
```java
|
||||
@Test
|
||||
void taskProgressIsHiddenUntilStartedAndResetsWhenFinished() {
|
||||
TaskProgressState state = new TaskProgressState();
|
||||
assertFalse(state.visible());
|
||||
state.start();
|
||||
assertTrue(state.visible());
|
||||
state.update(0.5);
|
||||
assertEquals(0.5, state.progress());
|
||||
state.finish();
|
||||
assertFalse(state.visible());
|
||||
assertEquals(0.0, state.progress());
|
||||
}
|
||||
|
||||
@Test
|
||||
void uniqueStartModuleIsSelectedAndCountIsReported() {
|
||||
ModuleLoadPresentation view = ModuleLoadPresentation.from(model("core", "app-start"), "");
|
||||
assertEquals(2, view.moduleCount());
|
||||
assertEquals("app-start", view.selectedModule().orElseThrow().artifactId());
|
||||
assertEquals("已检出 2 个 Maven 模块,已选择 app-start", view.message());
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleStartModulesRequireManualSelection() {
|
||||
ModuleLoadPresentation view = ModuleLoadPresentation.from(model("a-start", "b-start"), "");
|
||||
assertTrue(view.selectedModule().isEmpty());
|
||||
assertEquals("已检出 2 个 Maven 模块,请选择生成模块", view.message());
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行 RED**
|
||||
|
||||
Run: `mvn -Dtest=TaskProgressStateTest,ModuleLoadPresentationTest test`
|
||||
|
||||
Expected: FAIL,因为两个状态类型尚不存在。
|
||||
|
||||
- [ ] **Step 3: 实现最小状态类型**
|
||||
|
||||
`TaskProgressState` 将进度约束在 `0..1`;`finish()` 固定恢复隐藏和 0。`ModuleLoadPresentation` 优先使用仍存在的用户偏好,否则调用 `StartModuleSelector`,并生成包含模块数量的中文状态。
|
||||
|
||||
- [ ] **Step 4: 运行 GREEN**
|
||||
|
||||
Run: `mvn -Dtest=TaskProgressStateTest,ModuleLoadPresentationTest,StartModuleSelectorTest test`
|
||||
|
||||
Expected: 全部通过。
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add src/main/java/com/example/workutils/biz/aitest/ui/TaskProgressState.java src/main/java/com/example/workutils/biz/aitest/project/ModuleLoadPresentation.java src/test/java/com/example/workutils/biz/aitest/ui/TaskProgressStateTest.java src/test/java/com/example/workutils/biz/aitest/project/ModuleLoadPresentationTest.java
|
||||
git commit -m "feat: model AI test progress and module loading states"
|
||||
```
|
||||
|
||||
### Task 3: 四 Tab 页面与低密度表单
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/main/java/com/example/workutils/biz/aitest/AiTestPane.java`
|
||||
- Replace: `src/test/java/com/example/workutils/biz/aitest/AiTestPaneArchitectureTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `DirectoryPathSelector`、`TaskProgressState`、`ModuleLoadPresentation`。
|
||||
- Preserves: `AiTestRequest(project, module, baseline, targetRef, includeWorkingTree, settings)`。
|
||||
|
||||
- [ ] **Step 1: 写失败的 JavaFX 结构测试**
|
||||
|
||||
测试通过真实 `AiTestPane` 的节点树断言:中心是含四个指定 Tab 的 `TabPane`;不存在 `Accordion`;JDK/Maven 选择器路径框不可编辑;初始进度条不可见;基线和目标值分别为 `master` 与 `HEAD`。依赖使用 Mockito 返回空项目和空历史,测试只断言真实控件行为,不检查源码字符串。
|
||||
|
||||
- [ ] **Step 2: 运行 RED**
|
||||
|
||||
Run: `mvn -Dtest=AiTestPaneArchitectureTest test`
|
||||
|
||||
Expected: FAIL,因为中心仍是包含 Accordion 的 VBox,且进度条初始可见。
|
||||
|
||||
- [ ] **Step 3: 重构 `buildContent()`**
|
||||
|
||||
创建四个不可关闭主 Tab:项目页放项目、模块、baseline、targetRef、工作区选项和模块状态;工具链页放两个目录组件、CodeFree、覆盖率、修复轮次和检查按钮;质量规范页放左右 SplitPane;结果页放任务按钮、结果二级 Tab 与审核合并卡片。
|
||||
|
||||
- [ ] **Step 4: 接入目录组件**
|
||||
|
||||
删除 `jdk8Field`、`mavenField`,`settingsFromForm()` 改读 `jdkSelector.getPath()` 与 `mavenSelector.getPath()`;`applySettings()` 改调 `setPath()`。
|
||||
|
||||
- [ ] **Step 5: 接入模块展示**
|
||||
|
||||
`applyProjectData()` 使用 `ModuleLoadPresentation.from(...)`,一次性更新模块、选中值和状态文案;继续在成功和失败回调前检查 `projectLoadGate.isLatest(projectRequest)`。
|
||||
|
||||
- [ ] **Step 6: 接入进度展示**
|
||||
|
||||
初始配置加载、项目加载和模块偏好保存使用静默后台任务;`runAsync` 仅供用户主动任务。`setBusy(true)` 显示进度,`setBusy(false)` 隐藏并重置。项目页状态展示项目加载,不触发全局进度。
|
||||
|
||||
- [ ] **Step 7: 运行 GREEN**
|
||||
|
||||
Run: `mvn -Dtest=AiTestPaneArchitectureTest,LatestRequestGateTest,DirectoryPathSelectorTest,TaskProgressStateTest,ModuleLoadPresentationTest test`
|
||||
|
||||
Expected: 全部通过。
|
||||
|
||||
- [ ] **Step 8: 提交**
|
||||
|
||||
```bash
|
||||
git add src/main/java/com/example/workutils/biz/aitest/AiTestPane.java src/test/java/com/example/workutils/biz/aitest/AiTestPaneArchitectureTest.java
|
||||
git commit -m "feat: reorganize AI test plugin into four tabs"
|
||||
```
|
||||
|
||||
### Task 4: 回归保护与最终验证
|
||||
|
||||
**Files:**
|
||||
- Modify only if a regression is exposed: files already listed in Tasks 1-3
|
||||
- Verify: `src/test/java/com/example/workutils/biz/aitest/project/FtLoanCapitalCoreRegressionTest.java`
|
||||
- Verify: `src/test/java/com/example/workutils/biz/aitest/git/GitIncrementAnalyzerTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Preserves: `master...DEV_1156339215001460262` real-repository regression.
|
||||
- Preserves: latest project request wins.
|
||||
|
||||
- [ ] **Step 1: 运行 AI 插件测试**
|
||||
|
||||
Run: `mvn -Dtest='com.example.workutils.biz.aitest.**' test`
|
||||
|
||||
Expected: 0 failures;真实仓库存在时 `FtLoanCapitalCoreRegressionTest` 不跳过。
|
||||
|
||||
- [ ] **Step 2: 运行全量测试**
|
||||
|
||||
Run: `mvn test`
|
||||
|
||||
Expected: 0 failures,0 errors;仅允许既有无显示环境 GUI 测试跳过。
|
||||
|
||||
- [ ] **Step 3: 打包**
|
||||
|
||||
Run: `mvn -DskipTests package`
|
||||
|
||||
Expected: `target/workutils-1.0.1-SNAPSHOT.jar` 生成成功。
|
||||
|
||||
- [ ] **Step 4: 差异和工作树检查**
|
||||
|
||||
Run: `git diff --check` 与 `git status --short`
|
||||
|
||||
Expected: 无空白错误;用户原有审计文档、截图目录和 SQL 仍保持未跟踪且未暂存。
|
||||
|
||||
- [ ] **Step 5: 提交验证修正(仅当 Step 1-4 暴露并修复问题时)**
|
||||
|
||||
```bash
|
||||
git add src/main/java/com/example/workutils/biz/aitest src/test/java/com/example/workutils/biz/aitest
|
||||
git commit -m "fix: complete AI test plugin UI regression coverage"
|
||||
```
|
||||
Reference in New Issue
Block a user