feat: add thread status read views
This commit is contained in:
@@ -100,6 +100,38 @@ public class ConversationInfoActivityTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversationInfoShowsThreadStatusEntryForThreadConversation() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(ConversationInfoActivity.EXTRA_PROJECT_ID, "project-1")
|
||||
.putExtra(ConversationInfoActivity.EXTRA_PROJECT_NAME, "北区试产线回归");
|
||||
TestConversationInfoActivity activity = Robolectric
|
||||
.buildActivity(TestConversationInfoActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"renderConversation",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildDetailPayload()),
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildParticipantsPayload())
|
||||
);
|
||||
|
||||
View threadStatusRow = findClickableViewContainingText(
|
||||
activity.findViewById(R.id.screen_content),
|
||||
"线程状态"
|
||||
);
|
||||
assertNotNull(threadStatusRow);
|
||||
|
||||
threadStatusRow.performClick();
|
||||
|
||||
Intent nextIntent = Shadows.shadowOf(activity).getNextStartedActivity();
|
||||
assertNotNull(nextIntent);
|
||||
assertEquals(ThreadStatusActivity.class.getName(), nextIntent.getComponent().getClassName());
|
||||
assertEquals("project-1", nextIntent.getStringExtra(ThreadStatusActivity.EXTRA_PROJECT_ID));
|
||||
assertEquals("北区试产线回归", nextIntent.getStringExtra(ThreadStatusActivity.EXTRA_PROJECT_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversationInfoUsesOverflowMenuInTopBar() {
|
||||
Intent intent = new Intent()
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.hyzq.boss;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.AbstractExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(sdk = 34)
|
||||
public class ThreadStatusActivityTest {
|
||||
@Test
|
||||
public void reloadReadsThreadStatusAndRendersDocumentAndEvents() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(ThreadStatusActivity.EXTRA_PROJECT_ID, "project-1")
|
||||
.putExtra(ThreadStatusActivity.EXTRA_PROJECT_NAME, "北区试产线回归");
|
||||
TestThreadStatusActivity activity = Robolectric
|
||||
.buildActivity(TestThreadStatusActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
|
||||
RecordingBossApiClient apiClient = new RecordingBossApiClient(
|
||||
activity.getSharedPreferences("thread-status-test", Context.MODE_PRIVATE),
|
||||
"https://boss.hyzq.net"
|
||||
);
|
||||
ReflectionHelpers.setField(activity, "apiClient", apiClient);
|
||||
ReflectionHelpers.setField(activity, "executor", new DirectExecutorService());
|
||||
ReflectionHelpers.setField(activity, "reloadEnabled", true);
|
||||
|
||||
activity.reload();
|
||||
|
||||
LinearLayout content = activity.findViewById(R.id.screen_content);
|
||||
assertTrue(viewTreeContainsText(content, "当前目标"));
|
||||
assertTrue(viewTreeContainsText(content, "完成线程状态回归"));
|
||||
assertTrue(viewTreeContainsText(content, "当前阶段"));
|
||||
assertTrue(viewTreeContainsText(content, "增量同步"));
|
||||
assertTrue(viewTreeContainsText(content, "当前进度"));
|
||||
assertTrue(viewTreeContainsText(content, "已经记录最近 2 条进展"));
|
||||
assertTrue(viewTreeContainsText(content, "技术架构"));
|
||||
assertTrue(viewTreeContainsText(content, "Android 原生客户端 + Next.js API"));
|
||||
assertTrue(viewTreeContainsText(content, "当前阻塞"));
|
||||
assertTrue(viewTreeContainsText(content, "建议下一步"));
|
||||
assertTrue(viewTreeContainsText(content, "继续同步 Android 只读页"));
|
||||
assertTrue(viewTreeContainsText(content, "最近进展事件"));
|
||||
assertTrue(viewTreeContainsText(content, "事件 2"));
|
||||
assertTrue(viewTreeContainsText(content, "事件 1"));
|
||||
assertEquals("project-1", apiClient.lastRequestedProjectId);
|
||||
assertEquals("只读", String.valueOf(activity.findViewById(R.id.screen_header_action).getContentDescription()));
|
||||
}
|
||||
|
||||
private static final class TestThreadStatusActivity extends ThreadStatusActivity {
|
||||
private boolean reloadEnabled;
|
||||
|
||||
@Override
|
||||
protected void reload() {
|
||||
if (!reloadEnabled) {
|
||||
return;
|
||||
}
|
||||
super.reload();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RecordingBossApiClient extends BossApiClient {
|
||||
private String lastRequestedProjectId = "";
|
||||
|
||||
RecordingBossApiClient(android.content.SharedPreferences prefs, String baseUrl) {
|
||||
super(prefs, baseUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse getThreadStatus(String projectId) throws java.io.IOException, org.json.JSONException {
|
||||
lastRequestedProjectId = projectId;
|
||||
JSONObject payload = new JSONObject()
|
||||
.put("ok", true)
|
||||
.put("projectId", projectId)
|
||||
.put("threadStatusDocument", new JSONObject()
|
||||
.put("documentId", "doc-1")
|
||||
.put("projectId", projectId)
|
||||
.put("threadId", "thread-1")
|
||||
.put("threadDisplayName", "北区试产线回归")
|
||||
.put("folderName", "Boss")
|
||||
.put("deviceId", "mac-studio")
|
||||
.put("projectGoal", "完成线程状态回归")
|
||||
.put("currentPhase", "增量同步")
|
||||
.put("currentProgress", "已经记录最近 2 条进展")
|
||||
.put("technicalArchitecture", "Android 原生客户端 + Next.js API")
|
||||
.put("currentBlockers", "暂无阻塞")
|
||||
.put("recommendedNextStep", "继续同步 Android 只读页")
|
||||
.put("keyFiles", new JSONArray().put("ThreadStatusActivity.java"))
|
||||
.put("keyCommands", new JSONArray().put("./gradlew testDebugUnitTest"))
|
||||
.put("updatedAt", "2026-04-04T18:00:00+08:00")
|
||||
.put("sourceTaskId", "task-1")
|
||||
.put("sourceKind", "incremental_sync"))
|
||||
.put("recentProgressEvents", new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("eventId", "event-2")
|
||||
.put("projectId", projectId)
|
||||
.put("threadId", "thread-1")
|
||||
.put("threadDisplayName", "北区试产线回归")
|
||||
.put("deviceId", "mac-studio")
|
||||
.put("eventType", "progress_updated")
|
||||
.put("summary", "事件 2")
|
||||
.put("phase", "增量同步")
|
||||
.put("createdAt", "2026-04-04T18:02:00+08:00")
|
||||
.put("sourceTaskId", "task-2"))
|
||||
.put(new JSONObject()
|
||||
.put("eventId", "event-1")
|
||||
.put("projectId", projectId)
|
||||
.put("threadId", "thread-1")
|
||||
.put("threadDisplayName", "北区试产线回归")
|
||||
.put("deviceId", "mac-studio")
|
||||
.put("eventType", "progress_updated")
|
||||
.put("summary", "事件 1")
|
||||
.put("phase", "增量同步")
|
||||
.put("createdAt", "2026-04-04T18:01:00+08:00")
|
||||
.put("sourceTaskId", "task-1")));
|
||||
return new ApiResponse(200, payload);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DirectExecutorService extends AbstractExecutorService {
|
||||
private boolean shutdown;
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
shutdown = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Runnable> shutdownNow() {
|
||||
shutdown = true;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return shutdown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return shutdown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitTermination(long timeout, TimeUnit unit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
command.run();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean viewTreeContainsText(View root, String expectedText) {
|
||||
if (root instanceof TextView) {
|
||||
CharSequence text = ((TextView) root).getText();
|
||||
if (expectedText.contentEquals(text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!(root instanceof ViewGroup)) {
|
||||
return false;
|
||||
}
|
||||
ViewGroup group = (ViewGroup) root;
|
||||
for (int index = 0; index < group.getChildCount(); index += 1) {
|
||||
if (viewTreeContainsText(group.getChildAt(index), expectedText)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user