Files
suixinkan_ios_uikit/suixinkan_iosTests/MessageCenter/MessageCenterViewModelTests.swift
2026-06-26 14:33:31 +08:00

168 lines
6.5 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// MessageCenterViewModelTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/25.
//
import XCTest
@testable import suixinkan_ios
@MainActor
///
final class MessageCenterViewModelTests: XCTestCase {
func testReloadFirstPageMapsMessagesAndPaginationState() async {
let api = MessageCenterMock()
api.listResponses = [
MessageListResponse(hasMore: true, lastId: 101, items: [
MessageEntity(id: 101, type: 1, typeName: "", title: "新订单提醒", content: "有新订单", pushAt: "2026-06-25 10:00", isRead: false),
MessageEntity(id: 100, type: 2, typeName: "核销", title: "", content: "核销完成", pushAt: "2026-06-25 09:00", isRead: true)
])
]
let viewModel = MessageCenterViewModel()
viewModel.selectedFilter = .unread
await viewModel.reloadFirstPage(api: api)
XCTAssertEqual(api.listRequests.map(\.unread), [1])
XCTAssertEqual(viewModel.messages.map(\.id), ["msg_101", "msg_100"])
XCTAssertEqual(viewModel.messages[0].title, "新订单提醒")
XCTAssertEqual(viewModel.messages[0].type, .order)
XCTAssertEqual(viewModel.messages[1].title, "核销")
XCTAssertEqual(viewModel.messages[1].type, .writeOff)
XCTAssertEqual(viewModel.unreadCount, 1)
XCTAssertTrue(viewModel.hasMoreMessages)
XCTAssertEqual(viewModel.lastId, 101)
}
func testLoadMoreDeduplicatesSortsAndStopsAtTail() async {
let api = MessageCenterMock()
api.listResponses = [
MessageListResponse(hasMore: true, lastId: 101, items: [
MessageEntity(id: 101, type: 1, title: "A", content: "A", pushAt: "2026-06-25 10:00"),
MessageEntity(id: 100, type: 3, title: "B", content: "B", pushAt: "2026-06-25 09:00")
]),
MessageListResponse(hasMore: false, lastId: 99, items: [
MessageEntity(id: 100, type: 3, title: "B2", content: "B2", pushAt: "2026-06-25 09:00"),
MessageEntity(id: 99, type: 0, title: "C", content: "C", pushAt: "2026-06-24 08:00")
])
]
let viewModel = MessageCenterViewModel()
await viewModel.reloadFirstPage(api: api)
await viewModel.loadMore(api: api)
await viewModel.loadMore(api: api)
XCTAssertEqual(api.listRequests.map(\.lastId), [0, 101])
XCTAssertEqual(viewModel.messages.map(\.id), ["msg_101", "msg_100", "msg_99"])
XCTAssertEqual(viewModel.messages[1].title, "B2")
XCTAssertFalse(viewModel.hasMoreMessages)
XCTAssertEqual(viewModel.lastId, 99)
}
func testReloadFailureClearsStaleMessagesAndPaging() async {
let api = MessageCenterMock()
api.listResponses = [
MessageListResponse(hasMore: true, lastId: 101, items: [
MessageEntity(id: 101, type: 1, title: "A")
])
]
let viewModel = MessageCenterViewModel()
await viewModel.reloadFirstPage(api: api)
api.listError = TestError.sample
await viewModel.reloadFirstPage(api: api)
XCTAssertTrue(viewModel.messages.isEmpty)
XCTAssertFalse(viewModel.hasMoreMessages)
XCTAssertEqual(viewModel.lastId, 0)
XCTAssertTrue(viewModel.loadFailed)
}
func testMarkReadAllReadAndDeleteUpdateOnlyAfterSuccess() async throws {
let api = MessageCenterMock()
api.listResponses = [
MessageListResponse(items: [
MessageEntity(id: 101, type: 1, title: "A", isRead: false),
MessageEntity(id: 100, type: 0, title: "B", isRead: false)
])
]
let viewModel = MessageCenterViewModel()
await viewModel.reloadFirstPage(api: api)
try await viewModel.markAsRead(api: api, item: try XCTUnwrap(viewModel.messages.first))
XCTAssertEqual(api.readIds, [101])
XCTAssertEqual(viewModel.messages.map(\.isRead), [true, false])
api.readIds = []
try await viewModel.markAllAsRead(api: api)
XCTAssertEqual(api.readIds, [100])
XCTAssertTrue(viewModel.messages.allSatisfy(\.isRead))
let item = try XCTUnwrap(viewModel.messages.first)
try await viewModel.deleteMessage(api: api, item: item)
XCTAssertEqual(api.deleteIds, [101])
XCTAssertFalse(viewModel.messages.contains(where: { $0.id == item.id }))
}
func testFailedReadAndDeleteKeepLocalState() async throws {
let api = MessageCenterMock()
api.listResponses = [MessageListResponse(items: [MessageEntity(id: 101, type: 1, title: "A", isRead: false)])]
let viewModel = MessageCenterViewModel()
await viewModel.reloadFirstPage(api: api)
let item = try XCTUnwrap(viewModel.messages.first)
api.readError = TestError.sample
await XCTAssertThrowsErrorAsync(try await viewModel.markAsRead(api: api, item: item))
XCTAssertFalse(try XCTUnwrap(viewModel.messages.first).isRead)
api.readError = nil
api.deleteError = TestError.sample
await XCTAssertThrowsErrorAsync(try await viewModel.deleteMessage(api: api, item: item))
XCTAssertEqual(viewModel.messages.map(\.id), [item.id])
}
}
@MainActor
private final class MessageCenterMock: MessageCenterServing {
var listResponses: [MessageListResponse] = []
var listError: Error?
var readError: Error?
var deleteError: Error?
var readIds: [Int] = []
var deleteIds: [Int] = []
private(set) var listRequests: [(lastId: Int, limit: Int, unread: Int)] = []
func messageList(lastId: Int, limit: Int, unread: Int) async throws -> MessageListResponse {
listRequests.append((lastId, limit, unread))
if let listError { throw listError }
return listResponses.isEmpty ? MessageListResponse() : listResponses.removeFirst()
}
func messageRead(id: Int) async throws {
if let readError { throw readError }
readIds.append(id)
}
func messageDelete(id: Int) async throws {
if let deleteError { throw deleteError }
deleteIds.append(id)
}
}
private enum TestError: LocalizedError {
case sample
var errorDescription: String? { "测试错误" }
}
private func XCTAssertThrowsErrorAsync(
_ expression: @autoclosure () async throws -> Void,
file: StaticString = #filePath,
line: UInt = #line
) async {
do {
try await expression()
XCTFail("Expected error", file: file, line: line)
} catch {}
}