fix: route payment pushes to orders and prevent refresh crash
This commit is contained in:
@@ -24,6 +24,19 @@ final class DepositOrderListViewModelTests: XCTestCase {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testOrderNotificationResetsFilters() {
|
||||
let viewModel = DepositOrderListViewModel()
|
||||
viewModel.onSearchTextChange("123", api: OrderAPI(client: APIClient(environment: .testing, session: MockURLSession(results: []))))
|
||||
viewModel.onFilterChange(tab: 18)
|
||||
viewModel.onDateRangeSelected(start: Date(), end: Date())
|
||||
viewModel.resetFiltersForOrderNotification()
|
||||
XCTAssertEqual(viewModel.searchText, "")
|
||||
XCTAssertEqual(viewModel.tabIndex, 0)
|
||||
XCTAssertEqual(viewModel.filterLabel, "全部")
|
||||
XCTAssertNil(viewModel.startTime)
|
||||
XCTAssertNil(viewModel.endTime)
|
||||
}
|
||||
|
||||
func testStoreFilterMapsPaidStatusToAPI20() {
|
||||
XCTAssertEqual(DepositOrderListViewModel.apiOrderStatus(forTabIndex: 18), 20)
|
||||
XCTAssertEqual(DepositOrderListViewModel.apiOrderStatus(forTabIndex: 30), 30)
|
||||
|
||||
@@ -72,3 +72,88 @@ private extension UIColor {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,20 @@ final class OrderListViewModelTests: XCTestCase {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testOrderNotificationResetsFilters() {
|
||||
let viewModel = OrderListViewModel()
|
||||
viewModel.onSearchTextChange("13800138000")
|
||||
viewModel.onFilterChange(orderStatus: 18, isRefined: 1)
|
||||
viewModel.onDateRangeSelected(start: Date(), end: Date())
|
||||
viewModel.resetFiltersForOrderNotification()
|
||||
XCTAssertEqual(viewModel.searchText, "")
|
||||
XCTAssertEqual(viewModel.tabIndex, 0)
|
||||
XCTAssertEqual(viewModel.filterLabel, "全部")
|
||||
XCTAssertNil(viewModel.startTime)
|
||||
XCTAssertNil(viewModel.endTime)
|
||||
XCTAssertEqual(viewModel.rTabIndex, 0)
|
||||
}
|
||||
|
||||
func testOrderStatusQueryMapping() {
|
||||
XCTAssertNil(OrderListQueryBuilder.orderStatusQuery(tabIndex: 0))
|
||||
XCTAssertEqual(OrderListQueryBuilder.orderStatusQuery(tabIndex: 18), "18")
|
||||
|
||||
@@ -27,10 +27,10 @@ final class PushNotificationTests: XCTestCase {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testPayloadRoutesPaymentTypesToTheirPages() {
|
||||
func testPayloadRoutesOrderAndScanTypesToTheirPages() {
|
||||
XCTAssertEqual(
|
||||
PushPayload(userInfo: ["type": 1, "route": "message_center"]).destination,
|
||||
.paymentRecord
|
||||
.orders
|
||||
)
|
||||
XCTAssertEqual(
|
||||
PushPayload(userInfo: ["type": "6", "action": "task_management"]).destination,
|
||||
@@ -38,6 +38,36 @@ final class PushNotificationTests: XCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
func testOrderTypesSupportNumbersStringsAndVendorWrappers() {
|
||||
for type in [1, 5] {
|
||||
XCTAssertEqual(PushPayload(userInfo: ["type": type]).destination, .orders)
|
||||
XCTAssertEqual(PushPayload(userInfo: ["type": "\(type)"]).destination, .orders)
|
||||
XCTAssertEqual(PushPayload(userInfo: ["extras": "{\"type\":\(type)}"]).destination, .orders)
|
||||
XCTAssertEqual(PushPayload(values: ["type": "\(type)"]).destination, .orders)
|
||||
}
|
||||
}
|
||||
|
||||
func testOrderNotificationReturnsToOrdersRootAndDeduplicatesPendingRoute() {
|
||||
let coordinator = PushRouteCoordinator(appStore: appStore)
|
||||
let window = UIWindow(frame: UIScreen.main.bounds)
|
||||
coordinator.attach(window: window)
|
||||
coordinator.handle(destination: .orders, requestIdentifier: "order-request")
|
||||
authenticate(userID: "100")
|
||||
let mainTab = MainTabBarController()
|
||||
mainTab.loadViewIfNeeded()
|
||||
mainTab.selectTab(.orders)
|
||||
let ordersNavigation = mainTab.selectedViewController as? TabNavigationController
|
||||
ordersNavigation?.pushViewController(UIViewController(), animated: false)
|
||||
mainTab.selectTab(.home)
|
||||
window.rootViewController = mainTab
|
||||
coordinator.routePendingIfPossible()
|
||||
XCTAssertTrue(mainTab.selectedViewController === ordersNavigation)
|
||||
XCTAssertEqual(ordersNavigation?.viewControllers.count, 1)
|
||||
XCTAssertTrue(ordersNavigation?.topViewController is OrdersViewController)
|
||||
coordinator.handle(destination: .messageCenter, requestIdentifier: "order-request")
|
||||
XCTAssertTrue(mainTab.selectedViewController === ordersNavigation)
|
||||
}
|
||||
|
||||
func testPayloadIgnoresNonTypeRoutingFieldsAndOtherTypesFallBackToMessageCenter() {
|
||||
let conflictingFields: [AnyHashable: Any] = [
|
||||
"type": 2,
|
||||
@@ -46,7 +76,7 @@ final class PushNotificationTests: XCTestCase {
|
||||
"action": "收款",
|
||||
]
|
||||
|
||||
[0, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 100, 999].forEach { type in
|
||||
[0, 2, 3, 4, 7, 8, 9, 10, 11, 12, 100, 999].forEach { type in
|
||||
XCTAssertEqual(PushPayload(userInfo: ["type": type]).destination, .messageCenter)
|
||||
}
|
||||
XCTAssertEqual(PushPayload(userInfo: conflictingFields).destination, .messageCenter)
|
||||
@@ -220,7 +250,7 @@ final class PushNotificationTests: XCTestCase {
|
||||
payload: PushPayload(userInfo: ["type": 1, "route": "task_management"]),
|
||||
requestIdentifier: "request-1"
|
||||
)
|
||||
XCTAssertEqual(router.destinations, [.paymentRecord])
|
||||
XCTAssertEqual(router.destinations, [.orders])
|
||||
}
|
||||
|
||||
func testRemoteNotificationNotifiesUnreadMessageStateChange() async {
|
||||
|
||||
Reference in New Issue
Block a user