feat: 接入消息未读数、全部已读与首页红点角标同步。

固定消息中心为首页入口,并在推送到达、已读后刷新桌面角标。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-21 18:02:43 +08:00
parent e7f1d777dd
commit caeeb9a1cf
18 changed files with 748 additions and 37 deletions

View File

@ -90,10 +90,10 @@ final class HomeViewModelTests: XCTestCase {
let menuStore = HomeCommonMenuStore(defaults: defaults)
appStore.permissions.savePermissionItems([
HomePermissionItem(id: "1", name: "钱包", uri: "wallet"),
HomePermissionItem(id: "2", name: "云盘", uri: "cloud_management"),
HomePermissionItem(id: "3", name: "任务", uri: "task_management"),
HomePermissionItem(id: "4", name: "设置", uri: "system_settings"),
HomePermissionItem(id: "5", name: "消息", uri: "message_center"),
HomePermissionItem(id: "2", name: "消息", uri: "message_center"),
HomePermissionItem(id: "3", name: "云盘", uri: "cloud_management"),
HomePermissionItem(id: "4", name: "任务", uri: "task_management"),
HomePermissionItem(id: "5", name: "设置", uri: "system_settings"),
])
let viewModel = HomeViewModel(appStore: appStore, commonMenuStore: menuStore)
@ -231,6 +231,50 @@ final class HomeViewModelTests: XCTestCase {
XCTAssertEqual(session.requests.count, 1)
}
func testUnreadMessageStatusUsesCountEndpointAndUpdatesBadgeState() async {
let api = HomeMessageCenterAPIMock()
api.unreadCountResponses = [
MessageUnreadCountResponse(unreadCount: 9),
MessageUnreadCountResponse(unreadCount: 0),
]
let viewModel = HomeViewModel(appStore: appStore)
await viewModel.refreshUnreadMessageStatus(api: api)
XCTAssertTrue(viewModel.hasUnreadMessages)
XCTAssertEqual(viewModel.unreadMessageCount, 9)
await viewModel.refreshUnreadMessageStatus(api: api)
XCTAssertFalse(viewModel.hasUnreadMessages)
XCTAssertEqual(viewModel.unreadMessageCount, 0)
XCTAssertEqual(api.unreadCountCallCount, 2)
}
func testScenicHeaderMessageButtonShowsUnreadStateAndHandlesTap() throws {
let headerView = HomeScenicHeaderView(frame: CGRect(x: 0, y: 0, width: 390, height: 100))
var tapCount = 0
headerView.onMessageTap = { tapCount += 1 }
headerView.apply(scenicName: "测试景区", hasUnreadMessages: true)
headerView.layoutIfNeeded()
let button = try XCTUnwrap(
findSubview(in: headerView) { $0.accessibilityIdentifier == "home_message_button" } as? UIButton
)
let unreadDot = try XCTUnwrap(
findSubview(in: headerView) { $0.accessibilityIdentifier == "home_message_unread_dot" }
)
XCTAssertEqual(button.bounds.size, CGSize(width: 44, height: 44))
XCTAssertEqual(button.accessibilityLabel, "消息中心,有未读消息")
XCTAssertFalse(unreadDot.isHidden)
button.sendActions(for: .touchUpInside)
XCTAssertEqual(tapCount, 1)
headerView.apply(scenicName: "测试景区", hasUnreadMessages: false)
XCTAssertEqual(button.accessibilityLabel, "消息中心")
XCTAssertTrue(unreadDot.isHidden)
}
func testRefreshLocalDisplayStateClearsWhenNameMissing() {
appStore.session.currentScenicId = 10
appStore.session.currentScenicName = ""
@ -283,6 +327,45 @@ final class HomeViewModelTests: XCTestCase {
}
return nil
}
private func findSubview(in view: UIView, matching predicate: (UIView) -> Bool) -> UIView? {
if predicate(view) {
return view
}
for subview in view.subviews {
if let match = findSubview(in: subview, matching: predicate) {
return match
}
}
return nil
}
}
///
@MainActor
private final class HomeMessageCenterAPIMock: MessageCenterServing {
var unreadCountResponses: [MessageUnreadCountResponse] = []
var unreadCountCallCount = 0
func list(lastId: Int, limit: Int, unread: Int) async throws -> MessageListResponse {
MessageListResponse()
}
func unreadCount() async throws -> MessageUnreadCountResponse {
unreadCountCallCount += 1
guard !unreadCountResponses.isEmpty else { return MessageUnreadCountResponse() }
return unreadCountResponses.removeFirst()
}
func markAsRead(messageId: Int) async throws -> MessageUnreadCountResponse {
MessageUnreadCountResponse()
}
func markAllAsRead() async throws -> MessageReadAllResponse {
MessageReadAllResponse()
}
func delete(messageId: Int) async throws {}
}
private extension UIColor {