Files
suixinkan_uikit/suixinkanTests/OrderListUITokensTests.swift
T

160 lines
6.6 KiB
Swift

//
// OrderListUITokensTests.swift
// suixinkanTests
//
import UIKit
import XCTest
@testable import suixinkan
/// 订单列表 UI Token 与 badge 映射校验。
final class OrderListUITokensTests: XCTestCase {
func testOrderTokensMatchAndroid() {
XCTAssertEqual(OrderTokens.filterBackground.hexRGB, 0xF4F4F4)
XCTAssertEqual(OrderTokens.label563.hexRGB, 0x4B5563)
XCTAssertEqual(OrderTokens.timestamp.hexRGB, 0x6B7280)
XCTAssertEqual(OrderTokens.depositSuccess.hexRGB, 0x27AE60)
XCTAssertEqual(OrderTokens.depositRefunded.hexRGB, 0x6B7280)
XCTAssertEqual(OrderTokens.searchHeight, 36)
XCTAssertEqual(OrderTokens.filterPillHeight, 35)
XCTAssertEqual(OrderTokens.actionButtonHeight, 48)
XCTAssertEqual(OrderTokens.cardRadius, 12)
}
func testPhotographerOrderStatusBadgeMapping() {
let badge = AppStatusBadge()
badge.applyOrderStatus(18, text: "已付定金")
badge.applyOrderStatus(30, text: "已完成")
badge.applyOrderStatus(50, text: "已退款")
XCTAssertFalse(badge.isHidden)
}
func testDepositOrderStatus50UsesNeutralGray() {
let badge = AppStatusBadge()
badge.applyDepositOrderStatus(50, text: "已退款")
XCTAssertEqual(badge.backgroundColor?.hexRGB, OrderTokens.depositRefundedBackground.hexRGB)
}
func testDepositOrderStatus30UsesDepositGreen() {
let badge = AppStatusBadge()
badge.applyDepositOrderStatus(30, text: "已完成")
XCTAssertEqual(badge.backgroundColor?.hexRGB, OrderTokens.depositSuccessBackground.hexRGB)
}
func testPhotographerStatus50UsesDangerNotGray() {
let badge = AppStatusBadge()
badge.applyOrderStatus(50, text: "已退款")
XCTAssertEqual(badge.backgroundColor?.hexRGB, AppColor.dangerBackground.hexRGB)
}
func testOrderTypeChipIntrinsicSizeWrapsTextWithPadding() {
let chip = OrderTypeChipView()
chip.apply(text: "订单类型")
let size = chip.intrinsicContentSize
XCTAssertGreaterThan(size.width, "订单类型".size(withAttributes: [.font: UIFont.app(.caption)]).width)
XCTAssertGreaterThan(size.height, UIFont.app(.caption).lineHeight)
XCTAssertFalse(chip.isHidden)
}
}
private extension UIColor {
var hexRGB: UInt {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
let r = Int(round(red * 255))
let g = Int(round(green * 255))
let b = Int(round(blue * 255))
return UInt(r << 16 | g << 8 | b)
}
}
/// 回归通知刷新时表格仍请求旧行的场景,使用 mock 数据覆盖两种订单页面。
@MainActor
final class OrderNotificationListSnapshotTests: XCTestCase {
private var appStore: AppStore!
private var suiteName: String!
override func setUp() {
super.setUp()
suiteName = "OrderNotificationListSnapshotTests.\(UUID().uuidString)"
appStore = AppStore(defaults: UserDefaults(suiteName: suiteName)!)
appStore.session.configureTestAccount(userId: "push-list-test", accountType: .storeUser, scenicId: 10)
appStore.session.currentStoreId = 20
}
override func tearDown() {
UserDefaults.standard.removePersistentDomain(forName: suiteName)
appStore = nil
super.tearDown()
}
func testPhotographerRowsRemainReadableWhileNotificationRefreshClearsModel() async throws {
try await verifyRefreshUsesSnapshot(storeMode: false)
}
func testDepositRowsRemainReadableWhileNotificationRefreshClearsModel() async throws {
try await verifyRefreshUsesSnapshot(storeMode: true)
}
private func verifyRefreshUsesSnapshot(storeMode: Bool) async throws {
appStore.session.roleCode = storeMode ? AppRoleCode.storeAdmin.rawValue : AppRoleCode.photographer.rawValue
let initial = Data(#"{"code":100000,"msg":"success","data":{"list":[{"order_number":"push-order-a","order_status":30},{"order_number":"push-order-b","order_status":30}],"total":2}}"#.utf8)
let empty = Data(#"{"code":100000,"msg":"success","data":{"list":[],"total":0}}"#.utf8)
let session = MockURLSession(responses: [initial, empty, initial])
let api = OrderAPI(client: APIClient(environment: .testing, session: session))
let orders = OrderListViewModel(appStore: appStore)
let deposits = DepositOrderListViewModel(appStore: appStore)
let controller = OrdersViewController(orderAPI: api, orderListViewModel: orders, depositViewModel: deposits, appStore: appStore)
controller.openFromOrderNotification()
let list = try XCTUnwrap(controller.view.subviews.first { $0 is OrderListView } as? OrderListView)
let table = list.tableView
await waitForRows(2, in: table)
let source = try XCTUnwrap(table.dataSource)
// 保持表格的旧快照,精确模拟模型已清空、主线程 UI 回调尚未执行的窗口。
let notify = storeMode ? deposits.onStateChange : orders.onStateChange
if storeMode {
deposits.onStateChange = nil
deposits.resetFiltersForOrderNotification()
await deposits.refreshOrderList(api: api)
XCTAssertTrue(deposits.orderList.isEmpty)
} else {
orders.onStateChange = nil
orders.resetFiltersForOrderNotification()
await orders.refreshOrderList(api: api)
XCTAssertTrue(orders.orderList.isEmpty)
}
XCTAssertEqual(source.tableView(table, numberOfRowsInSection: 0), 2)
for index in 0..<2 {
let cell = source.tableView(table, cellForRowAt: IndexPath(row: index, section: 0))
if storeMode {
XCTAssertTrue(cell is DepositOrderCardCell)
} else {
XCTAssertTrue(cell is OrderCardCell)
}
}
if storeMode { deposits.onStateChange = notify } else { orders.onStateChange = notify }
notify?()
await waitForRows(0, in: table)
// 已打开的订单页再次点击通知,仍能加载完整列表。
controller.openFromOrderNotification()
await waitForRows(2, in: table)
XCTAssertEqual(session.requests.count, 3)
}
private func waitForRows(_ count: Int, in table: UITableView) async {
let applied = expectation(for: NSPredicate { _, _ in
MainActor.assumeIsolated {
table.numberOfRows(inSection: 0) == count
}
}, evaluatedWith: nil)
await fulfillment(of: [applied], timeout: 3)
}
}