feat: streamline group dispatch reminders

This commit is contained in:
kris
2026-04-04 03:00:34 +08:00
parent 425d8992ef
commit 5ebb37cbfc
13 changed files with 485 additions and 37 deletions

View File

@@ -161,6 +161,29 @@ public class GroupInfoActivityTest {
assertTrue(viewTreeContainsText(content, "OMX Team Runtime 当前可用,当前可切换到该后端。"));
}
@Test
public void renderGroupShowsLightReminderPreferenceRow() throws Exception {
Intent intent = new Intent()
.putExtra(GroupInfoActivity.EXTRA_PROJECT_ID, "group-1")
.putExtra(GroupInfoActivity.EXTRA_PROJECT_NAME, "巡检协作群");
TestGroupInfoActivity activity = Robolectric
.buildActivity(TestGroupInfoActivity.class, intent)
.setup()
.get();
ReflectionHelpers.callInstanceMethod(
activity,
"renderGroup",
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildDetailPayload(true)),
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildParticipantsPayload()),
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildOrchestrationBackendPayload())
);
LinearLayout content = activity.findViewById(R.id.screen_content);
assertTrue(viewTreeContainsText(content, "推荐下发默认轻提醒"));
assertTrue(viewTreeContainsText(content, "已开启"));
}
@Test
public void renderGroupShowsOmxFallbackHintWhenOmxRuntimeIsUnavailable() throws Exception {
Intent intent = new Intent()
@@ -236,7 +259,38 @@ public class GroupInfoActivityTest {
assertEquals("{\"requestedBackendId\":\"omx-team\"}", connection.requestBody());
}
@Test
public void saveDispatchReminderPreferenceUsesScopedEndpoint() throws Exception {
Intent intent = new Intent()
.putExtra(GroupInfoActivity.EXTRA_PROJECT_ID, "group-1")
.putExtra(GroupInfoActivity.EXTRA_PROJECT_NAME, "巡检协作群");
TestGroupInfoActivity activity = Robolectric
.buildActivity(TestGroupInfoActivity.class, intent)
.setup()
.get();
RecordingConnection connection = new RecordingConnection(
new URL("https://boss.hyzq.net/api/v1/projects/group-1/dispatch-reminder")
);
ReflectionHelpers.setField(activity, "apiClient", new RecordingBossApiClient(connection));
ReflectionHelpers.setField(activity, "executor", new DirectExecutorService());
ReflectionHelpers.callInstanceMethod(
activity,
"saveDispatchReminderPreference",
ReflectionHelpers.ClassParameter.from(boolean.class, true)
);
Shadows.shadowOf(Looper.getMainLooper()).idle();
assertEquals("/api/v1/projects/group-1/dispatch-reminder", connection.lastPath);
assertEquals("PATCH", connection.requestMethodValue);
assertEquals("{\"lightDispatchReminderEnabled\":true}", connection.requestBody());
}
private static JSONObject buildDetailPayload() throws Exception {
return buildDetailPayload(false);
}
private static JSONObject buildDetailPayload(boolean lightReminderEnabled) throws Exception {
JSONObject threadMeta = new JSONObject()
.put("threadId", "group-thread-3")
.put("folderName", "Boss");
@@ -245,6 +299,7 @@ public class GroupInfoActivityTest {
.put("name", "巡检协作群")
.put("isGroup", true)
.put("collaborationMode", "development")
.put("lightDispatchReminderEnabled", lightReminderEnabled)
.put("threadMeta", threadMeta);
return new JSONObject().put("project", project);
}

View File

@@ -463,10 +463,45 @@ public class ProjectDetailActivityUiTest {
ReflectionHelpers.ClassParameter.from(JSONObject.class, dispatchPlan)
);
assertTrue(viewTreeContainsText(card, "确认下"));
assertTrue(viewTreeContainsText(card, "确认"));
assertTrue(viewTreeContainsText(card, "确认并记住"));
assertTrue(viewTreeContainsText(card, "拒绝"));
}
@Test
public void pendingDispatchPlanViewUsesCompactCopyWhenLightReminderEnabled() throws Exception {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-group")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "协作群");
TestProjectDetailActivity activity = Robolectric
.buildActivity(TestProjectDetailActivity.class, intent)
.setup()
.get();
ReflectionHelpers.setField(activity, "projectCollaborationMode", "approval_required");
ReflectionHelpers.setField(activity, "projectApprovalState", "pending_user");
ReflectionHelpers.setField(activity, "lightDispatchReminderEnabled", true);
JSONObject dispatchPlan = new JSONObject()
.put("planId", "dispatch-plan-light")
.put("summary", "建议先让 Boss 移动控制台同步树莓派二代状态。")
.put("targets", new JSONArray().put(new JSONObject()
.put("projectId", "thread-1")
.put("threadDisplayName", "Boss 移动控制台")
.put("reason", "最近活跃")));
View card = ReflectionHelpers.callInstanceMethod(
activity,
"buildPendingDispatchPlanView",
ReflectionHelpers.ClassParameter.from(JSONObject.class, dispatchPlan)
);
assertTrue(viewTreeContainsText(card, "主 Agent 已推荐 1 个线程"));
assertTrue(viewTreeContainsText(card, "继续下发"));
assertFalse(viewTreeContainsText(card, "等待你批准主 Agent 下发"));
assertFalse(viewTreeContainsText(card, "当前确认状态:"));
}
@Test
public void renderProjectShowsRepairEntryForDirtyGroupAndOpensGroupInfo() throws Exception {
Intent intent = new Intent()