feat: 接入消息未读数、全部已读与首页红点角标同步。
固定消息中心为首页入口,并在推送到达、已读后刷新桌面角标。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -32,17 +32,77 @@ final class MessageCenterViewModelTests: XCTestCase {
|
||||
func testUnreadSelectionMarksReadAndOpensDetail() async {
|
||||
let api = MockMessageCenterAPI()
|
||||
api.listResponses = [MessageListResponse(hasMore: false, lastId: 0, items: [makeMessage(id: 3, isRead: false)])]
|
||||
api.readResponse = MessageUnreadCountResponse(unreadCount: 4)
|
||||
let viewModel = MessageCenterViewModel()
|
||||
var opened: MessageItem?
|
||||
var callbackCount: Int?
|
||||
viewModel.onOpenDetail = { opened = $0 }
|
||||
viewModel.onUnreadMessageCountChange = { callbackCount = $0 }
|
||||
let unreadChanged = expectation(
|
||||
forNotification: NotificationName.unreadMessageCountDidChange,
|
||||
object: nil,
|
||||
handler: { notification in
|
||||
notification.userInfo?[NotificationUserInfoKey.unreadCount] as? Int == 4
|
||||
}
|
||||
)
|
||||
|
||||
await viewModel.loadInitial(api: api)
|
||||
await viewModel.selectMessage(id: 3, api: api)
|
||||
await fulfillment(of: [unreadChanged], timeout: 1)
|
||||
|
||||
XCTAssertEqual(api.readCalls, [3])
|
||||
XCTAssertEqual(opened?.id, 3)
|
||||
XCTAssertEqual(opened?.isRead, true)
|
||||
XCTAssertEqual(viewModel.items.first?.isRead, true)
|
||||
XCTAssertEqual(callbackCount, 4)
|
||||
}
|
||||
|
||||
func testMarkAllAsReadUpdatesItemsAndPublishesLatestCount() async {
|
||||
let api = MockMessageCenterAPI()
|
||||
api.listResponses = [MessageListResponse(items: [
|
||||
makeMessage(id: 1, isRead: false),
|
||||
makeMessage(id: 2, isRead: true),
|
||||
])]
|
||||
api.readAllResponse = MessageReadAllResponse(updatedCount: 1, unreadCount: 0)
|
||||
let viewModel = MessageCenterViewModel()
|
||||
var callbackCount: Int?
|
||||
var message: String?
|
||||
viewModel.onUnreadMessageCountChange = { callbackCount = $0 }
|
||||
viewModel.onShowMessage = { message = $0 }
|
||||
let unreadChanged = expectation(
|
||||
forNotification: NotificationName.unreadMessageCountDidChange,
|
||||
object: nil,
|
||||
handler: { notification in
|
||||
notification.userInfo?[NotificationUserInfoKey.unreadCount] as? Int == 0
|
||||
}
|
||||
)
|
||||
|
||||
await viewModel.loadInitial(api: api)
|
||||
let response = await viewModel.markAllAsRead(api: api)
|
||||
await fulfillment(of: [unreadChanged], timeout: 1)
|
||||
|
||||
XCTAssertEqual(api.readAllCallCount, 1)
|
||||
XCTAssertEqual(response, MessageReadAllResponse(updatedCount: 1, unreadCount: 0))
|
||||
XCTAssertTrue(viewModel.items.allSatisfy(\.isRead))
|
||||
XCTAssertEqual(callbackCount, 0)
|
||||
XCTAssertEqual(message, "已全部标记为已读")
|
||||
}
|
||||
|
||||
func testMarkAllAsReadFailureKeepsUnreadState() async {
|
||||
let api = MockMessageCenterAPI()
|
||||
api.listResponses = [MessageListResponse(items: [makeMessage(id: 6, isRead: false)])]
|
||||
api.readAllError = TestError(message: "fail")
|
||||
let viewModel = MessageCenterViewModel()
|
||||
var message: String?
|
||||
viewModel.onShowMessage = { message = $0 }
|
||||
|
||||
await viewModel.loadInitial(api: api)
|
||||
let response = await viewModel.markAllAsRead(api: api)
|
||||
|
||||
XCTAssertNil(response)
|
||||
XCTAssertEqual(api.readAllCallCount, 1)
|
||||
XCTAssertEqual(viewModel.items.first?.isRead, false)
|
||||
XCTAssertEqual(message, "全部标记已读失败,请稍后重试")
|
||||
}
|
||||
|
||||
func testReadSelectionOpensDetailWithoutCallingReadAPI() async {
|
||||
@ -125,8 +185,12 @@ private final class MockMessageCenterAPI: MessageCenterServing {
|
||||
var listResponses: [MessageListResponse] = []
|
||||
var listCalls: [(lastId: Int, limit: Int, unread: Int)] = []
|
||||
var readCalls: [Int] = []
|
||||
var readResponse = MessageUnreadCountResponse()
|
||||
var readAllResponse = MessageReadAllResponse()
|
||||
var readAllCallCount = 0
|
||||
var deleteCalls: [Int] = []
|
||||
var readError: Error?
|
||||
var readAllError: Error?
|
||||
var deleteError: Error?
|
||||
|
||||
func list(lastId: Int, limit: Int, unread: Int) async throws -> MessageListResponse {
|
||||
@ -135,9 +199,20 @@ private final class MockMessageCenterAPI: MessageCenterServing {
|
||||
return listResponses.removeFirst()
|
||||
}
|
||||
|
||||
func markAsRead(messageId: Int) async throws {
|
||||
func unreadCount() async throws -> MessageUnreadCountResponse {
|
||||
MessageUnreadCountResponse()
|
||||
}
|
||||
|
||||
func markAsRead(messageId: Int) async throws -> MessageUnreadCountResponse {
|
||||
readCalls.append(messageId)
|
||||
if let readError { throw readError }
|
||||
return readResponse
|
||||
}
|
||||
|
||||
func markAllAsRead() async throws -> MessageReadAllResponse {
|
||||
readAllCallCount += 1
|
||||
if let readAllError { throw readAllError }
|
||||
return readAllResponse
|
||||
}
|
||||
|
||||
func delete(messageId: Int) async throws {
|
||||
|
||||
Reference in New Issue
Block a user