新增门店订单与核销管理列表,对齐 Android 订单能力并完善账号级缓存。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
224
suixinkanTests/StoreAdminOrderListViewModelTests.swift
Normal file
224
suixinkanTests/StoreAdminOrderListViewModelTests.swift
Normal file
@ -0,0 +1,224 @@
|
||||
//
|
||||
// StoreAdminOrderListViewModelTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
// Created by Codex on 2026/6/29.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
@MainActor
|
||||
/// 店长订单 Tab ViewModel 测试,覆盖列表分页、筛选与退款参数。
|
||||
final class StoreAdminOrderListViewModelTests: XCTestCase {
|
||||
/// 测试无门店时清空列表且不请求接口。
|
||||
func testReloadClearsDataWhenStoreIsMissing() async throws {
|
||||
let api = StoreAdminMockOrderService()
|
||||
api.responses = [try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success")]
|
||||
let viewModel = StoreAdminOrderListViewModel()
|
||||
|
||||
try await viewModel.reload(api: api, storeId: 66)
|
||||
XCTAssertFalse(viewModel.orders.isEmpty)
|
||||
|
||||
try await viewModel.reload(api: api, storeId: nil)
|
||||
|
||||
XCTAssertTrue(viewModel.orders.isEmpty)
|
||||
XCTAssertEqual(viewModel.total, 0)
|
||||
XCTAssertEqual(viewModel.page, 1)
|
||||
XCTAssertFalse(viewModel.hasMore)
|
||||
XCTAssertEqual(api.listCalls.count, 1)
|
||||
}
|
||||
|
||||
/// 测试筛选参数会传入服务层并重置到第一页。
|
||||
func testReloadUsesFilterQueries() async throws {
|
||||
let api = StoreAdminMockOrderService()
|
||||
api.responses = [
|
||||
try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success"),
|
||||
try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success")
|
||||
]
|
||||
let viewModel = StoreAdminOrderListViewModel()
|
||||
viewModel.searchPhone = " 13800000000 "
|
||||
viewModel.selectedStatus = 30
|
||||
viewModel.filterStartDate = Self.date("2026-05-01")
|
||||
viewModel.filterEndDate = Self.date("2026-05-31")
|
||||
|
||||
try await viewModel.reload(api: api, storeId: 66)
|
||||
XCTAssertEqual(api.listCalls.count, 1)
|
||||
XCTAssertEqual(api.listCalls[0].storeId, 66)
|
||||
XCTAssertEqual(api.listCalls[0].userPhone, "13800000000")
|
||||
XCTAssertEqual(api.listCalls[0].orderStatus, 30)
|
||||
XCTAssertEqual(api.listCalls[0].startTime, "2026-05-01")
|
||||
XCTAssertEqual(api.listCalls[0].endTime, "2026-05-31")
|
||||
|
||||
viewModel.selectedStatus = 0
|
||||
viewModel.searchPhone = ""
|
||||
try await viewModel.reload(api: api, storeId: 66)
|
||||
XCTAssertEqual(api.listCalls[1].orderStatus, nil)
|
||||
XCTAssertNil(api.listCalls[1].userPhone)
|
||||
}
|
||||
|
||||
/// 测试最后一页不会继续请求。
|
||||
func testLoadMoreStopsAfterLastPage() async throws {
|
||||
let api = StoreAdminMockOrderService()
|
||||
api.responses = [try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success")]
|
||||
let viewModel = StoreAdminOrderListViewModel()
|
||||
|
||||
try await viewModel.reload(api: api, storeId: 66)
|
||||
try await viewModel.loadMore(api: api, storeId: 66)
|
||||
|
||||
XCTAssertEqual(viewModel.orders.count, 2)
|
||||
XCTAssertEqual(api.listCalls.count, 1)
|
||||
XCTAssertFalse(viewModel.hasMore)
|
||||
}
|
||||
|
||||
/// 测试店长退款会传入门店与订单参数。
|
||||
func testRefundUsesStoreParameters() async throws {
|
||||
let api = StoreAdminMockOrderService()
|
||||
let viewModel = StoreAdminOrderListViewModel()
|
||||
|
||||
try await viewModel.refund(
|
||||
api: api,
|
||||
storeId: 66,
|
||||
orderNumber: "STORE001",
|
||||
mode: .partial,
|
||||
amount: "50.00",
|
||||
reason: "部分退款"
|
||||
)
|
||||
|
||||
XCTAssertEqual(api.refundCalls.count, 1)
|
||||
XCTAssertEqual(api.refundCalls[0].storeId, 66)
|
||||
XCTAssertEqual(api.refundCalls[0].orderNumber, "STORE001")
|
||||
XCTAssertEqual(api.refundCalls[0].mode, .partial)
|
||||
XCTAssertEqual(api.refundCalls[0].amount, "50.00")
|
||||
XCTAssertEqual(api.refundCalls[0].reason, "部分退款")
|
||||
}
|
||||
|
||||
private static func date(_ text: String) -> Date {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
return formatter.date(from: text)!
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// 店长订单列表测试替身,记录 storeOrderList 与退款调用。
|
||||
private final class StoreAdminMockOrderService: OrderServing {
|
||||
struct ListCall: Equatable {
|
||||
let storeId: Int?
|
||||
let page: Int
|
||||
let userPhone: String?
|
||||
let orderStatus: Int?
|
||||
let startTime: String?
|
||||
let endTime: String?
|
||||
}
|
||||
|
||||
struct RefundCall: Equatable {
|
||||
let storeId: Int
|
||||
let orderNumber: String
|
||||
let mode: OrderRefundMode
|
||||
let amount: String
|
||||
let reason: String
|
||||
}
|
||||
|
||||
var responses: [ListPayload<StoreOrderListItem>] = []
|
||||
private(set) var listCalls: [ListCall] = []
|
||||
private(set) var refundCalls: [RefundCall] = []
|
||||
|
||||
func orderList(
|
||||
scenicId: Int,
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
orderStatus: Int?,
|
||||
userPhone: String?,
|
||||
startTime: String?,
|
||||
endTime: String?,
|
||||
isRefined: Int?,
|
||||
isScenicAdmin: Bool
|
||||
) async throws -> ListPayload<OrderEntity> {
|
||||
ListPayload(total: 0, list: [])
|
||||
}
|
||||
|
||||
func writeOffList(
|
||||
scenicId: Int,
|
||||
storeId: Int?,
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
orderStatus: Int?,
|
||||
orderVerifyStatus: Int?,
|
||||
startTime: String?,
|
||||
endTime: String?,
|
||||
userPhone: String?
|
||||
) async throws -> ListPayload<WriteOffOrderItem> {
|
||||
ListPayload(total: 0, list: [])
|
||||
}
|
||||
|
||||
func updateRefinedChoice(orderNumber: String, isRefined: Int) async throws {}
|
||||
func cancelOrder(orderNumber: String) async throws {}
|
||||
func sendGift(orderNumber: String, photoNum: Int, videoNum: Int) async throws {}
|
||||
|
||||
func storeOrderList(
|
||||
storeId: Int?,
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
userPhone: String?,
|
||||
orderStatus: Int?,
|
||||
orderType: Int?,
|
||||
startTime: String?,
|
||||
endTime: String?
|
||||
) async throws -> ListPayload<StoreOrderListItem> {
|
||||
listCalls.append(
|
||||
ListCall(
|
||||
storeId: storeId,
|
||||
page: page,
|
||||
userPhone: userPhone,
|
||||
orderStatus: orderStatus,
|
||||
startTime: startTime,
|
||||
endTime: endTime
|
||||
)
|
||||
)
|
||||
return responses.isEmpty ? ListPayload(total: 0, list: []) : responses.removeFirst()
|
||||
}
|
||||
|
||||
func storeOrderRefund(
|
||||
storeId: Int,
|
||||
orderNumber: String,
|
||||
refundType: OrderRefundMode,
|
||||
refundAmount: String,
|
||||
refundReason: String
|
||||
) async throws {
|
||||
refundCalls.append(
|
||||
RefundCall(
|
||||
storeId: storeId,
|
||||
orderNumber: orderNumber,
|
||||
mode: refundType,
|
||||
amount: refundAmount,
|
||||
reason: refundReason
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func writeOff(orderNumber: String) async throws {}
|
||||
func storeOrderDetail(storeId: Int, orderNumber: String) async throws -> StoreOrderDetailResponse {
|
||||
try TestFixture.payload(StoreOrderDetailResponse.self, named: "store_order_detail_success")
|
||||
}
|
||||
func depositOrderList(scenicId: Int, page: Int, pageSize: Int) async throws -> ListPayload<DepositOrderListItem> {
|
||||
ListPayload(total: 0, list: [])
|
||||
}
|
||||
func depositOrderWriteOff(orderNumber: String) async throws {}
|
||||
func depositOrderRefund(orderNumber: String, refundReason: String) async throws {}
|
||||
func storeOrderShootingDetail(
|
||||
storeId: Int,
|
||||
orderNumber: String,
|
||||
scenicSpotId: Int,
|
||||
photogUid: Int
|
||||
) async throws -> StoreOrderShootingDetailResponse {
|
||||
try TestFixture.payload(StoreOrderShootingDetailResponse.self, named: "store_order_shooting_detail_success")
|
||||
}
|
||||
func orderRefund(orderNumber: String, refundType: OrderRefundMode, refundAmount: String, refundReason: String) async throws {}
|
||||
func multiTravelShootHistory(orderNumber: String) async throws -> MultiTravelShootHistoryResponse {
|
||||
try TestFixture.payload(MultiTravelShootHistoryResponse.self, named: "multi_travel_shoot_history_success")
|
||||
}
|
||||
func multiTravelVerifiedScenicSpotList(orderNumber: String) async throws -> [MultiTravelVerifiedScenicSpotItem] { [] }
|
||||
func multiTravelUploadMaterial(_ request: MultiTravelUploadMaterialRequest) async throws {}
|
||||
}
|
||||
Reference in New Issue
Block a user