移除订单 Tab 内门店与核销订单详情页及跳转入口。
列表卡片仅展示摘要,保留订单来源绑定与押金订单详情等其它子流程。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -16,8 +16,6 @@ final class NavigationRouterTests: XCTestCase {
|
||||
XCTAssertTrue(AppRoute.home(.moreFunctions).hidesTabBarWhenPushed)
|
||||
XCTAssertTrue(AppRoute.home(.paymentCollection).hidesTabBarWhenPushed)
|
||||
XCTAssertTrue(AppRoute.home(.wallet).hidesTabBarWhenPushed)
|
||||
XCTAssertTrue(AppRoute.orders(.storeDetail(try! Self.firstOrder())).hidesTabBarWhenPushed)
|
||||
XCTAssertTrue(AppRoute.orders(.writeOffDetail(try! Self.firstWriteOffOrder())).hidesTabBarWhenPushed)
|
||||
XCTAssertTrue(AppRoute.orders(.multiTravelTaskUpload(orderNumber: "MT001")).hidesTabBarWhenPushed)
|
||||
XCTAssertTrue(AppRoute.orders(.orderTrailer(orderNumber: "MT001", title: "尾片上传")).hidesTabBarWhenPushed)
|
||||
}
|
||||
@ -75,32 +73,14 @@ final class NavigationRouterTests: XCTestCase {
|
||||
}
|
||||
|
||||
/// 测试订单模块真实路由可被压入导航栈。
|
||||
func testOrderRoutesCanBePushedIntoRouterPath() throws {
|
||||
func testOrderRoutesCanBePushedIntoRouterPath() {
|
||||
let router = RouterPath()
|
||||
let order = try Self.firstOrder()
|
||||
let writeOff = try Self.firstWriteOffOrder()
|
||||
|
||||
router.navigate(to: .orders(.storeDetail(order)))
|
||||
router.navigate(to: .orders(.writeOffDetail(writeOff)))
|
||||
router.navigate(to: .orders(.multiTravelTaskUpload(orderNumber: "MT001")))
|
||||
router.navigate(to: .orders(.orderTrailer(orderNumber: "MT001", title: "视频预告")))
|
||||
|
||||
XCTAssertEqual(router.path.count, 4)
|
||||
XCTAssertEqual(router.path[0], .orders(.storeDetail(order)))
|
||||
XCTAssertEqual(router.path[1], .orders(.writeOffDetail(writeOff)))
|
||||
XCTAssertEqual(router.path[2], .orders(.multiTravelTaskUpload(orderNumber: "MT001")))
|
||||
XCTAssertEqual(router.path[3], .orders(.orderTrailer(orderNumber: "MT001", title: "视频预告")))
|
||||
}
|
||||
|
||||
/// 返回测试用订单管理列表项。
|
||||
private static func firstOrder() throws -> OrderEntity {
|
||||
let payload = try TestFixture.payload(ListPayload<OrderEntity>.self, named: "order_v2_success")
|
||||
return try XCTUnwrap(payload.list.first)
|
||||
}
|
||||
|
||||
/// 返回测试用核销订单列表项。
|
||||
private static func firstWriteOffOrder() throws -> WriteOffOrderItem {
|
||||
let payload = try TestFixture.payload(ListPayload<WriteOffOrderItem>.self, named: "writeoff_list_success")
|
||||
return try XCTUnwrap(payload.list.first)
|
||||
XCTAssertEqual(router.path.count, 2)
|
||||
XCTAssertEqual(router.path[0], .orders(.multiTravelTaskUpload(orderNumber: "MT001")))
|
||||
XCTAssertEqual(router.path[1], .orders(.orderTrailer(orderNumber: "MT001", title: "视频预告")))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,169 +0,0 @@
|
||||
//
|
||||
// OrderDetailViewModelTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
// Created by Codex on 2026/6/22.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
@MainActor
|
||||
/// 订单详情 ViewModel 测试,覆盖详情加载、缺失上下文和错误兜底。
|
||||
final class OrderDetailViewModelTests: XCTestCase {
|
||||
/// 测试有门店 ID 时请求详情接口并展示服务端字段。
|
||||
func testLoadRequestsDetailWhenStoreIdExists() async throws {
|
||||
let item = try firstOrder()
|
||||
let api = DetailMockOrderService()
|
||||
api.detailResponses = [try TestFixture.payload(StoreOrderDetailResponse.self, named: "store_order_detail_success")]
|
||||
let viewModel = OrderDetailViewModel(item: item)
|
||||
|
||||
await viewModel.load(api: api, fallbackStoreId: 66)
|
||||
|
||||
XCTAssertEqual(api.detailCalls.count, 1)
|
||||
XCTAssertEqual(api.detailCalls[0].storeId, 66)
|
||||
XCTAssertEqual(api.detailCalls[0].orderNumber, "ORDV2001")
|
||||
XCTAssertEqual(viewModel.display.projectName, "亲子跟拍")
|
||||
XCTAssertEqual(viewModel.projectInfo?.settleSpotNum, 3)
|
||||
XCTAssertEqual(viewModel.shootingList.first?.scenicSpotName, "东门")
|
||||
XCTAssertNil(viewModel.errorMessage)
|
||||
}
|
||||
|
||||
/// 测试缺少门店 ID 时不请求接口并保留列表摘要。
|
||||
func testLoadWithoutStoreIdUsesSummaryOnly() async throws {
|
||||
let item = try firstOrder()
|
||||
let api = DetailMockOrderService()
|
||||
let viewModel = OrderDetailViewModel(item: item)
|
||||
|
||||
await viewModel.load(api: api, fallbackStoreId: nil)
|
||||
|
||||
XCTAssertTrue(api.detailCalls.isEmpty)
|
||||
XCTAssertEqual(viewModel.display.orderNumber, "ORDV2001")
|
||||
XCTAssertEqual(viewModel.contextMessage, "缺少门店上下文,当前展示订单摘要。")
|
||||
}
|
||||
|
||||
/// 测试详情接口失败时保留摘要并暴露错误信息。
|
||||
func testLoadFailureKeepsSummaryAndExposesError() async throws {
|
||||
let item = try firstOrder()
|
||||
let api = DetailMockOrderService()
|
||||
api.detailError = APIError.httpStatus(500, "server error")
|
||||
let viewModel = OrderDetailViewModel(item: item)
|
||||
|
||||
await viewModel.load(api: api, fallbackStoreId: 66)
|
||||
|
||||
XCTAssertEqual(api.detailCalls.count, 1)
|
||||
XCTAssertEqual(viewModel.display.orderNumber, "ORDV2001")
|
||||
XCTAssertEqual(viewModel.contextMessage, "详情加载失败,当前展示订单摘要。")
|
||||
XCTAssertNotNil(viewModel.errorMessage)
|
||||
}
|
||||
|
||||
/// 测试详情模型兼容后端数字和字符串混合字段。
|
||||
func testDetailResponseDecodesMixedNumericAndStringFields() throws {
|
||||
let detail = try TestFixture.payload(StoreOrderDetailResponse.self, named: "store_order_detail_success")
|
||||
|
||||
XCTAssertEqual(detail.orderType, 19)
|
||||
XCTAssertEqual(detail.actualRefundAmount, "0")
|
||||
XCTAssertEqual(detail.userId, 7001)
|
||||
XCTAssertEqual(detail.multiTravel?.projectInfo?.singleSpotVideoNum, 1)
|
||||
XCTAssertEqual(detail.multiTravel?.shootingList.first?.startAvg, 4.8)
|
||||
}
|
||||
|
||||
/// 返回订单列表 fixture 中的第一条订单。
|
||||
private func firstOrder() throws -> OrderEntity {
|
||||
let payload = try TestFixture.payload(ListPayload<OrderEntity>.self, named: "order_v2_success")
|
||||
return try XCTUnwrap(payload.list.first)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// 订单详情服务测试替身,专门用于详情 ViewModel 测试。
|
||||
private final class DetailMockOrderService: OrderServing {
|
||||
var detailResponses: [StoreOrderDetailResponse] = []
|
||||
var detailError: Error?
|
||||
private(set) var detailCalls: [(storeId: Int, orderNumber: String)] = []
|
||||
|
||||
/// 订单列表接口在详情测试中不会被调用。
|
||||
func orderList(
|
||||
scenicId: Int,
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
orderStatus: Int?,
|
||||
userPhone: String?,
|
||||
startTime: String?,
|
||||
endTime: String?,
|
||||
isRefined: Int?,
|
||||
isScenicAdmin: Bool
|
||||
) async throws -> ListPayload<OrderEntity> {
|
||||
XCTFail("orderList should not be called")
|
||||
return ListPayload(total: 0, list: [])
|
||||
}
|
||||
|
||||
/// 核销列表接口在详情测试中不会被调用。
|
||||
func writeOffList(scenicId: Int, storeId: Int?, page: Int, pageSize: Int) async throws -> ListPayload<WriteOffOrderItem> {
|
||||
XCTFail("writeOffList should not be called")
|
||||
return ListPayload(total: 0, list: [])
|
||||
}
|
||||
|
||||
/// 核销接口在详情测试中不会被调用。
|
||||
func writeOff(orderNumber: String) async throws {
|
||||
XCTFail("writeOff should not be called")
|
||||
}
|
||||
|
||||
/// 返回预置的订单详情响应。
|
||||
func storeOrderDetail(storeId: Int, orderNumber: String) async throws -> StoreOrderDetailResponse {
|
||||
detailCalls.append((storeId: storeId, orderNumber: orderNumber))
|
||||
if let detailError {
|
||||
throw detailError
|
||||
}
|
||||
return detailResponses.isEmpty ? try TestFixture.payload(StoreOrderDetailResponse.self, named: "store_order_detail_success") : detailResponses.removeFirst()
|
||||
}
|
||||
|
||||
/// 押金订单列表接口在详情测试中不会被调用。
|
||||
func depositOrderList(scenicId: Int, page: Int, pageSize: Int) async throws -> ListPayload<DepositOrderListItem> {
|
||||
XCTFail("depositOrderList should not be called")
|
||||
return ListPayload(total: 0, list: [])
|
||||
}
|
||||
|
||||
/// 押金核销接口在详情测试中不会被调用。
|
||||
func depositOrderWriteOff(orderNumber: String) async throws {
|
||||
XCTFail("depositOrderWriteOff should not be called")
|
||||
}
|
||||
|
||||
/// 押金退款接口在详情测试中不会被调用。
|
||||
func depositOrderRefund(orderNumber: String, refundReason: String) async throws {
|
||||
XCTFail("depositOrderRefund should not be called")
|
||||
}
|
||||
|
||||
/// 拍摄信息接口在详情测试中不会被调用。
|
||||
func storeOrderShootingDetail(
|
||||
storeId: Int,
|
||||
orderNumber: String,
|
||||
scenicSpotId: Int,
|
||||
photogUid: Int
|
||||
) async throws -> StoreOrderShootingDetailResponse {
|
||||
XCTFail("storeOrderShootingDetail should not be called")
|
||||
return try TestFixture.payload(StoreOrderShootingDetailResponse.self, named: "store_order_shooting_detail_success")
|
||||
}
|
||||
|
||||
/// 普通退款接口在详情测试中不会被调用。
|
||||
func orderRefund(orderNumber: String, refundType: OrderRefundMode, refundAmount: String, refundReason: String) async throws {
|
||||
XCTFail("orderRefund should not be called")
|
||||
}
|
||||
|
||||
/// 历史拍摄接口在详情测试中不会被调用。
|
||||
func multiTravelShootHistory(orderNumber: String) async throws -> MultiTravelShootHistoryResponse {
|
||||
XCTFail("multiTravelShootHistory should not be called")
|
||||
return try TestFixture.payload(MultiTravelShootHistoryResponse.self, named: "multi_travel_shoot_history_success")
|
||||
}
|
||||
|
||||
/// 已核销打卡点接口在详情测试中不会被调用。
|
||||
func multiTravelVerifiedScenicSpotList(orderNumber: String) async throws -> [MultiTravelVerifiedScenicSpotItem] {
|
||||
XCTFail("multiTravelVerifiedScenicSpotList should not be called")
|
||||
return []
|
||||
}
|
||||
|
||||
/// 多点旅拍素材提交接口在详情测试中不会被调用。
|
||||
func multiTravelUploadMaterial(_ request: MultiTravelUploadMaterialRequest) async throws {
|
||||
XCTFail("multiTravelUploadMaterial should not be called")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user