// // 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) } }