feat: restore wechat thread ui and group chat
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
package com.hyzq.boss;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AboutActivityStaleDownloadCleanupTest {
|
||||
@Test
|
||||
public void collectStaleDownloadIdsForRemoval_returnsIdsWhenReleaseChanged() throws Exception {
|
||||
JSONObject availableRelease = new StubJSONObject()
|
||||
.withString("packageFileName", "boss-android-v1.2.9-release.apk")
|
||||
.withString("version", "v1.2.9");
|
||||
|
||||
long[] ids = AboutActivity.collectStaleDownloadIdsForRemoval(
|
||||
availableRelease,
|
||||
"boss-android-v1.2.8-release.apk",
|
||||
"v1.2.8",
|
||||
true,
|
||||
42L,
|
||||
77L
|
||||
);
|
||||
|
||||
assertArrayEquals(new long[]{42L, 77L}, ids);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void collectStaleDownloadIdsForRemoval_returnsEmptyWhenReleaseMatchesLocalPackage() throws Exception {
|
||||
JSONObject availableRelease = new StubJSONObject()
|
||||
.withString("packageFileName", "boss-android-v1.2.9-release.apk")
|
||||
.withString("version", "v1.2.9");
|
||||
|
||||
long[] ids = AboutActivity.collectStaleDownloadIdsForRemoval(
|
||||
availableRelease,
|
||||
"boss-android-v1.2.9-release.apk",
|
||||
"v1.2.9",
|
||||
true,
|
||||
42L,
|
||||
77L
|
||||
);
|
||||
|
||||
assertArrayEquals(new long[0], ids);
|
||||
}
|
||||
|
||||
private static final class StubJSONObject extends JSONObject {
|
||||
private final java.util.Map<String, Object> values = new java.util.HashMap<>();
|
||||
|
||||
StubJSONObject withString(String key, String value) {
|
||||
values.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String optString(String key) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof String ? (String) value : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String optString(String key, String fallback) {
|
||||
String value = optString(key);
|
||||
return value.isEmpty() ? fallback : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.hyzq.boss;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GroupCreateActivityTest {
|
||||
@Test
|
||||
public void collectSelectableConversationItems_filtersOutExistingGroupChats() {
|
||||
JSONObject threadConversation = new StubJSONObject()
|
||||
.withString("projectId", "thread-1")
|
||||
.withString("projectTitle", "线程一")
|
||||
.withBoolean("isGroup", false);
|
||||
JSONObject groupConversation = new StubJSONObject()
|
||||
.withString("projectId", "group-1")
|
||||
.withString("projectTitle", "已有群聊")
|
||||
.withBoolean("isGroup", true);
|
||||
JSONObject sourceConversation = new StubJSONObject()
|
||||
.withString("projectId", "source-1")
|
||||
.withString("projectTitle", "来源线程")
|
||||
.withBoolean("isGroup", false);
|
||||
JSONObject conversationsPayload = new StubJSONObject()
|
||||
.withObjectArray("conversations", threadConversation, groupConversation, sourceConversation);
|
||||
|
||||
java.util.List<JSONObject> filtered = GroupCreateActivity.collectSelectableConversationItems(conversationsPayload, "source-1");
|
||||
|
||||
assertEquals(1, filtered.size());
|
||||
assertEquals("thread-1", filtered.get(0).optString("projectId", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reconcileSelectedProjectIds_keepsManualDeselectionWhenCandidatesStayTheSame() {
|
||||
Set<String> previousCandidateIds = linkedSet("thread-1", "thread-2", "thread-3");
|
||||
Set<String> currentSelectedIds = linkedSet("thread-1", "thread-3");
|
||||
Set<String> nextCandidateIds = linkedSet("thread-1", "thread-2", "thread-3");
|
||||
|
||||
Set<String> reconciled = GroupCreateActivity.reconcileSelectedProjectIds(
|
||||
currentSelectedIds,
|
||||
previousCandidateIds,
|
||||
nextCandidateIds
|
||||
);
|
||||
|
||||
assertEquals(2, reconciled.size());
|
||||
assertTrue(reconciled.contains("thread-1"));
|
||||
assertTrue(reconciled.contains("thread-3"));
|
||||
assertFalse(reconciled.contains("thread-2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCreateGroupChat_blocksWhileRefreshingOrCreating() {
|
||||
Set<String> selectedProjectIds = linkedSet("thread-1");
|
||||
|
||||
assertFalse(GroupCreateActivity.canCreateGroupChat(true, false, selectedProjectIds));
|
||||
assertFalse(GroupCreateActivity.canCreateGroupChat(false, true, selectedProjectIds));
|
||||
assertTrue(GroupCreateActivity.canCreateGroupChat(false, false, selectedProjectIds));
|
||||
assertFalse(GroupCreateActivity.canCreateGroupChat(false, false, linkedSet()));
|
||||
}
|
||||
|
||||
private static Set<String> linkedSet(String... values) {
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
for (String value : values) {
|
||||
result.add(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final class StubJSONObject extends JSONObject {
|
||||
private final java.util.Map<String, Object> values = new java.util.HashMap<>();
|
||||
|
||||
StubJSONObject withString(String key, String value) {
|
||||
values.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
StubJSONObject withBoolean(String key, boolean value) {
|
||||
values.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
StubJSONObject withObjectArray(String key, JSONObject... entries) {
|
||||
values.put(key, new StubJSONArray(entries));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String optString(String key, String defaultValue) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof String ? (String) value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean optBoolean(String key, boolean defaultValue) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof Boolean ? (Boolean) value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONArray optJSONArray(String key) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof JSONArray ? (JSONArray) value : null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StubJSONArray extends JSONArray {
|
||||
private final JSONObject[] entries;
|
||||
|
||||
StubJSONArray(JSONObject... entries) {
|
||||
this.entries = entries == null ? new JSONObject[0] : entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return entries.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject optJSONObject(int index) {
|
||||
if (index < 0 || index >= entries.length) {
|
||||
return null;
|
||||
}
|
||||
return entries[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,102 @@
|
||||
package com.hyzq.boss;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class WechatSurfaceMapperTest {
|
||||
@Test
|
||||
public void toConversationRow_keepsOnlyWechatFields() throws Exception {
|
||||
public void toConversationRow_mapsWechatConversationFieldsFromThreadPayload() throws Exception {
|
||||
JSONObject item = new StubJSONObject()
|
||||
.withString("projectTitle", "项目 A")
|
||||
.withString("preview", "最近消息预览")
|
||||
.withString("latestReplyLabel", "10:24")
|
||||
.withString("threadTitle", "北区试产线回归")
|
||||
.withString("folderLabel", "归档确认")
|
||||
.withString("preview", "旧预览")
|
||||
.withString("lastMessagePreview", "现场摄像头关键帧")
|
||||
.withString("latestReplyLabel", "09:26")
|
||||
.withInt("unreadCount", 3)
|
||||
.withString("deviceNamesPreview", "Mac Studio")
|
||||
.withBoolean("contextBudgetIndicator", true);
|
||||
.withInt("activityIconCount", 2)
|
||||
.withString("topPinnedLabel", "置顶")
|
||||
.withBoolean("isGroup", false)
|
||||
.withObject("avatar", new StubJSONObject()
|
||||
.withString("primary", "M")
|
||||
.withString("secondary", "W"))
|
||||
.withObjectArray("groupMembers",
|
||||
new StubJSONObject().withString("threadId", "t-1").withString("avatar", "M").withString("title", "Mac Studio"),
|
||||
new StubJSONObject().withString("threadId", "t-2").withString("avatar", "W").withString("title", "Windows GPU"));
|
||||
|
||||
WechatSurfaceMapper.ConversationRow row = WechatSurfaceMapper.toConversationRow(item);
|
||||
|
||||
assertEquals("项目 A", row.title);
|
||||
assertEquals("最近消息预览", row.preview);
|
||||
assertEquals("10:24", row.timeLabel);
|
||||
assertEquals("北区试产线回归", row.threadTitle);
|
||||
assertEquals("归档确认", row.folderLabel);
|
||||
assertEquals("现场摄像头关键帧", row.lastMessagePreview);
|
||||
assertEquals("09:26", row.timeLabel);
|
||||
assertEquals(3, row.unreadCount);
|
||||
assertEquals("置顶", row.topPinnedLabel);
|
||||
assertEquals(2, row.activityIconCount);
|
||||
assertFalse(row.isGroup);
|
||||
assertEquals("M", row.avatarPrimary);
|
||||
assertEquals("W", row.avatarSecondary);
|
||||
assertEquals(2, row.groupAvatarMembers.length);
|
||||
assertEquals("Mac Studio", row.groupAvatarMembers[0].title);
|
||||
assertEquals("W", row.groupAvatarMembers[1].avatarLabel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDeviceRow_keepsOnlySimpleSubtitle() throws Exception {
|
||||
public void toConversationRow_prefersGroupMembersForGroupAvatarSummary() throws Exception {
|
||||
JSONObject item = new StubJSONObject()
|
||||
.withString("projectTitle", "群聊项目")
|
||||
.withString("threadTitle", "容灾切换验证")
|
||||
.withString("folderLabel", "Mac + Windows 协作")
|
||||
.withString("lastMessagePreview", "最新: API 切换记录回传")
|
||||
.withString("latestReplyLabel", "09:12")
|
||||
.withInt("activityIconCount", 2)
|
||||
.withString("topPinnedLabel", "置顶")
|
||||
.withBoolean("isGroup", true)
|
||||
.withObjectArray("groupMembers",
|
||||
new StubJSONObject().withString("threadId", "group-1").withString("avatar", "M").withString("title", "Mac Studio"),
|
||||
new StubJSONObject().withString("threadId", "group-2").withString("avatar", "W").withString("title", "Windows GPU"),
|
||||
new StubJSONObject().withString("threadId", "group-3").withString("avatar", "C").withString("title", "Cloud Backup"));
|
||||
|
||||
WechatSurfaceMapper.ConversationRow row = WechatSurfaceMapper.toConversationRow(item);
|
||||
|
||||
assertTrue(row.isGroup);
|
||||
assertEquals("容灾切换验证", row.threadTitle);
|
||||
assertEquals("Mac + Windows 协作", row.folderLabel);
|
||||
assertEquals("最新: API 切换记录回传", row.lastMessagePreview);
|
||||
assertEquals("09:12", row.timeLabel);
|
||||
assertEquals(3, row.groupAvatarMembers.length);
|
||||
assertEquals("M", row.groupAvatarMembers[0].avatarLabel);
|
||||
assertEquals("Cloud Backup", row.groupAvatarMembers[2].title);
|
||||
assertEquals("", row.avatarPrimary);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDeviceRow_mapsLegacyWechatThreeLineSummary() throws Exception {
|
||||
JSONObject item = new StubJSONObject()
|
||||
.withString("name", "Mac Studio")
|
||||
.withString("avatar", "M")
|
||||
.withString("status", "online")
|
||||
.withString("account", "17600003315")
|
||||
.withStringArray("projects", "北区试产线回归", "容灾切换验证")
|
||||
.withInt("quota5h", 8)
|
||||
.withInt("quota7d", 22);
|
||||
|
||||
WechatSurfaceMapper.DeviceRow row = WechatSurfaceMapper.toDeviceRow(item);
|
||||
|
||||
assertEquals("Mac Studio", row.title);
|
||||
assertEquals("在线 · 17600003315", row.subtitle);
|
||||
assertEquals("账号: 17600003315 · 项目: 北区试产线回归 / 容灾切换验证", row.subtitle);
|
||||
assertEquals("额度: 5h 8% · 7d 22%", row.meta);
|
||||
assertEquals("M", row.avatarLabel);
|
||||
assertEquals("online", row.statusKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -50,7 +109,9 @@ public class WechatSurfaceMapperTest {
|
||||
WechatSurfaceMapper.DeviceRow row = WechatSurfaceMapper.toDeviceRow(item);
|
||||
|
||||
assertEquals("Mac Studio", row.title);
|
||||
assertEquals("异常 · 17600003315", row.subtitle);
|
||||
assertEquals("账号: 17600003315", row.subtitle);
|
||||
assertEquals("额度: 暂无 · 状态异常", row.meta);
|
||||
assertEquals("abnormal", row.statusKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -61,19 +122,19 @@ public class WechatSurfaceMapperTest {
|
||||
.withString("account", "17600003315")
|
||||
.withString("note", "书房主机")
|
||||
.withString("endpoint", "https://boss.hyzq.net/device/mac-studio")
|
||||
.withArray("projects", "master-agent", "android-app");
|
||||
.withStringArray("projects", "master-agent", "android-app");
|
||||
|
||||
WechatSurfaceMapper.DeviceDetailSummary summary = WechatSurfaceMapper.toDeviceDetailSummary(item);
|
||||
|
||||
assertEquals("Mac Studio", summary.title);
|
||||
assertEquals("在线 · 17600003315", summary.subtitle);
|
||||
assertEquals("书房主机 · https://boss.hyzq.net/device/mac-studio · 项目 master-agent, android-app", summary.meta);
|
||||
assertEquals("账号: 17600003315 · 项目: master-agent / android-app", summary.subtitle);
|
||||
assertEquals("额度: 暂无 · 书房主机 · https://boss.hyzq.net/device/mac-studio · 项目 master-agent, android-app", summary.meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rootMeMenuTitles_matchApprovedSimpleMenu() throws Exception {
|
||||
public void rootMeMenuTitles_matchLegacyWechatMenuWithOpsEntry() throws Exception {
|
||||
assertArrayEquals(
|
||||
new String[]{"账号与安全", "AI 账号", "设置", "技能", "关于"},
|
||||
new String[]{"账号与安全", "设置", "运维与修复", "AI 账号", "技能", "关于"},
|
||||
WechatSurfaceMapper.rootMeMenuTitles()
|
||||
);
|
||||
}
|
||||
@@ -87,16 +148,112 @@ public class WechatSurfaceMapperTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mainPage_doesNotExposeOpsEntry() throws Exception {
|
||||
public void mainPage_keepsOpsEntryInStableWechatMenuOrder() throws Exception {
|
||||
assertArrayEquals(
|
||||
new String[]{"账号与安全", "AI 账号", "设置", "技能", "关于"},
|
||||
new String[]{"账号与安全", "设置", "运维与修复", "AI 账号", "技能", "关于"},
|
||||
WechatSurfaceMapper.rootMeMenuTitles()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void advancedEntryTitle_movesOpsOutOfMainMePage() throws Exception {
|
||||
assertEquals("高级与调试", WechatSurfaceMapper.advancedEntryTitle());
|
||||
public void opsEntryCopy_staysInMeFlowWithoutLegacyAdvancedEntrySemantics() throws Exception {
|
||||
WechatSurfaceMapper.MeMenuItem opsItem = WechatSurfaceMapper.findMeMenuItem("ops");
|
||||
|
||||
assertNotNull(opsItem);
|
||||
assertEquals("运维与修复", opsItem.title);
|
||||
assertEquals("查看运维会话、修复回放与 standby 切换", opsItem.description);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void meFlow_doesNotExposeAuditConversationCopy() throws Exception {
|
||||
for (WechatSurfaceMapper.MeMenuItem item : WechatSurfaceMapper.rootMeMenuItems()) {
|
||||
assertFalse(item.title.contains("审计"));
|
||||
assertFalse(item.description.contains("审计"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aboutActivity_parsesStructuredOtaSummaryArrayIntoReadableContent() throws Exception {
|
||||
JSONObject ota = new StubJSONObject()
|
||||
.withObject("availableRelease", new StubJSONObject()
|
||||
.withString("version", "v1.2.8")
|
||||
.withStringArray("summary", "优化设备状态刷新", "修复主 Agent 会话排序", "提升 OTA 回收稳定性"));
|
||||
|
||||
java.lang.reflect.Method method = AboutActivity.class.getDeclaredMethod("buildOtaContentBody", JSONObject.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
String content = (String) method.invoke(null, ota);
|
||||
|
||||
assertEquals("版本 v1.2.8\n1. 优化设备状态刷新\n2. 修复主 Agent 会话排序\n3. 提升 OTA 回收稳定性", content);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aboutActivity_rejectsStaleDownloadedApkWhenAvailableReleaseChanged() throws Exception {
|
||||
JSONObject availableRelease = new StubJSONObject()
|
||||
.withString("version", "v1.2.9")
|
||||
.withString("packageFileName", "boss-android-v1.2.9-release.apk");
|
||||
|
||||
java.lang.reflect.Method method = AboutActivity.class.getDeclaredMethod(
|
||||
"isDownloadedReleaseCurrent",
|
||||
JSONObject.class,
|
||||
String.class,
|
||||
String.class
|
||||
);
|
||||
method.setAccessible(true);
|
||||
|
||||
boolean stillCurrent = (Boolean) method.invoke(
|
||||
null,
|
||||
availableRelease,
|
||||
"boss-android-v1.2.8-release.apk",
|
||||
"v1.2.8"
|
||||
);
|
||||
|
||||
assertFalse(stillCurrent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skillInventory_fallsBackWhenExplicitDeviceIdIsInvalid() throws Exception {
|
||||
JSONArray devices = new StubObjectArray(
|
||||
new StubJSONObject()
|
||||
.withString("id", "device-b")
|
||||
.withString("account", "17600003315"),
|
||||
new StubJSONObject()
|
||||
.withString("id", "device-c")
|
||||
.withString("account", "other-account")
|
||||
);
|
||||
|
||||
java.lang.reflect.Method method = SkillInventoryActivity.class.getDeclaredMethod(
|
||||
"chooseTargetDeviceId",
|
||||
String.class,
|
||||
String.class,
|
||||
String.class,
|
||||
JSONArray.class
|
||||
);
|
||||
method.setAccessible(true);
|
||||
|
||||
String resolved = (String) method.invoke(
|
||||
null,
|
||||
"stale-device-id",
|
||||
"missing-bound-device",
|
||||
"17600003315",
|
||||
devices
|
||||
);
|
||||
|
||||
assertEquals("device-b", resolved);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aboutActivity_collectsAllPositiveDownloadIdsForStaleRemoval() throws Exception {
|
||||
java.lang.reflect.Method method = AboutActivity.class.getDeclaredMethod(
|
||||
"collectDownloadIdsForRemoval",
|
||||
long.class,
|
||||
long.class
|
||||
);
|
||||
method.setAccessible(true);
|
||||
|
||||
long[] ids = (long[]) method.invoke(null, 42L, 77L);
|
||||
|
||||
assertArrayEquals(new long[]{42L, 77L}, ids);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -107,6 +264,16 @@ public class WechatSurfaceMapperTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectDetailInfoTarget_routesSingleThreadsToConversationInfo() {
|
||||
assertEquals(ConversationInfoActivity.class, WechatSurfaceMapper.resolveConversationInfoTargetClass(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectDetailInfoTarget_routesGroupChatsToGroupInfo() {
|
||||
assertEquals(GroupInfoActivity.class, WechatSurfaceMapper.resolveConversationInfoTargetClass(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectPrimarySections_keepOnlyChatEssentials() throws Exception {
|
||||
assertArrayEquals(
|
||||
@@ -115,6 +282,66 @@ public class WechatSurfaceMapperTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversationsChrome_copyStaysLightweightInsteadOfConsoleInstructions() throws Exception {
|
||||
assertEquals("会话", WechatSurfaceMapper.loginTitle());
|
||||
assertEquals("进入会话", WechatSurfaceMapper.loginButtonLabel());
|
||||
assertEquals("项目自动对应设备 GUI 项目文件夹", WechatSurfaceMapper.conversationsHintPillText());
|
||||
assertEquals("", WechatSurfaceMapper.conversationsHeaderSubtitle());
|
||||
assertFalse(WechatSurfaceMapper.loginHintText().contains("Boss API"));
|
||||
assertFalse(WechatSurfaceMapper.loginHintText().contains("控制台"));
|
||||
assertFalse(WechatSurfaceMapper.loginHintText().contains("数据仍来自现有 Boss API"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversationActivityIcons_useAnimatedDotViewsInsteadOfTextGlyphs() throws Exception {
|
||||
assertEquals("animated_dots", WechatSurfaceMapper.conversationActivityIconMode());
|
||||
assertEquals(4, WechatSurfaceMapper.maxConversationActivityIcons());
|
||||
assertEquals("cancel_on_detach", WechatSurfaceMapper.conversationActivityAnimationCleanup());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void meMenuItems_useStableKeysInsteadOfDisplayTitlesForRouting() throws Exception {
|
||||
WechatSurfaceMapper.MeMenuItem[] items = WechatSurfaceMapper.rootMeMenuItems();
|
||||
|
||||
assertEquals(6, items.length);
|
||||
assertEquals("security", items[0].key);
|
||||
assertEquals("账号与安全", items[0].title);
|
||||
assertEquals("settings", items[1].key);
|
||||
assertEquals("ops", items[2].key);
|
||||
assertEquals("运维与修复", items[2].title);
|
||||
assertEquals("ai_accounts", items[3].key);
|
||||
assertEquals("skills", items[4].key);
|
||||
assertEquals("about", items[5].key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshMergePolicy_appliesSuccessfulPayloadsWithoutDroppingCachedValues() throws Exception {
|
||||
JSONArray cachedConversations = new StubStringArray("cached-conversation");
|
||||
JSONArray freshConversations = new StubStringArray("fresh-conversation");
|
||||
JSONArray cachedDevices = new StubStringArray("cached-device");
|
||||
JSONObject cachedOta = new StubJSONObject().withString("version", "1.0.0");
|
||||
JSONObject freshSettings = new StubJSONObject().withString("preferredEntryPoint", "devices");
|
||||
|
||||
assertSame(
|
||||
freshConversations,
|
||||
WechatSurfaceMapper.resolveRefreshValue(cachedConversations, freshConversations, true)
|
||||
);
|
||||
assertSame(
|
||||
cachedDevices,
|
||||
WechatSurfaceMapper.resolveRefreshValue(cachedDevices, new StubStringArray("fresh-device"), false)
|
||||
);
|
||||
assertSame(
|
||||
cachedOta,
|
||||
WechatSurfaceMapper.resolveRefreshValue(cachedOta, new StubJSONObject().withString("version", "2.0.0"), false)
|
||||
);
|
||||
assertSame(
|
||||
freshSettings,
|
||||
WechatSurfaceMapper.resolveRefreshValue(null, freshSettings, true)
|
||||
);
|
||||
assertNull(WechatSurfaceMapper.resolveRefreshValue(null, null, false));
|
||||
}
|
||||
|
||||
private static final class StubJSONObject extends JSONObject {
|
||||
private final java.util.Map<String, Object> values = new java.util.HashMap<>();
|
||||
|
||||
@@ -133,8 +360,18 @@ public class WechatSurfaceMapperTest {
|
||||
return this;
|
||||
}
|
||||
|
||||
StubJSONObject withArray(String key, String... entries) {
|
||||
values.put(key, new StubJSONArray(entries));
|
||||
StubJSONObject withObject(String key, JSONObject value) {
|
||||
values.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
StubJSONObject withStringArray(String key, String... entries) {
|
||||
values.put(key, new StubStringArray(entries));
|
||||
return this;
|
||||
}
|
||||
|
||||
StubJSONObject withObjectArray(String key, JSONObject... entries) {
|
||||
values.put(key, new StubObjectArray(entries));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -157,16 +394,22 @@ public class WechatSurfaceMapperTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.json.JSONArray optJSONArray(String key) {
|
||||
public JSONObject optJSONObject(String key) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof org.json.JSONArray ? (org.json.JSONArray) value : null;
|
||||
return value instanceof JSONObject ? (JSONObject) value : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONArray optJSONArray(String key) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof JSONArray ? (JSONArray) value : null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StubJSONArray extends org.json.JSONArray {
|
||||
private static final class StubStringArray extends JSONArray {
|
||||
private final String[] entries;
|
||||
|
||||
StubJSONArray(String... entries) {
|
||||
StubStringArray(String... entries) {
|
||||
this.entries = entries == null ? new String[0] : entries;
|
||||
}
|
||||
|
||||
@@ -180,8 +423,28 @@ public class WechatSurfaceMapperTest {
|
||||
if (index < 0 || index >= entries.length) {
|
||||
return "";
|
||||
}
|
||||
String value = entries[index];
|
||||
return value == null ? "" : value;
|
||||
return entries[index] == null ? "" : entries[index];
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StubObjectArray extends JSONArray {
|
||||
private final JSONObject[] entries;
|
||||
|
||||
StubObjectArray(JSONObject... entries) {
|
||||
this.entries = entries == null ? new JSONObject[0] : entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return entries.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject optJSONObject(int index) {
|
||||
if (index < 0 || index >= entries.length) {
|
||||
return null;
|
||||
}
|
||||
return entries[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user