222 lines
8.2 KiB
Swift
222 lines
8.2 KiB
Swift
//
|
||
// TravelAlbumViewModelTests.swift
|
||
// suixinkanTests
|
||
//
|
||
|
||
import XCTest
|
||
@testable import suixinkan
|
||
|
||
@MainActor
|
||
/// 旅拍相册 ViewModel 测试。
|
||
final class TravelAlbumEntryViewModelTests: XCTestCase {
|
||
func testLoadAlbumsUpdatesListAndTotal() async {
|
||
let api = TravelAlbumMockAPI()
|
||
api.listResponse = TravelAlbumListResponse(total: 1, list: [TravelAlbum(id: 1, name: "A")])
|
||
let viewModel = TravelAlbumEntryViewModel(currentScenicIdProvider: { 1 })
|
||
|
||
await viewModel.loadAlbums(api: api)
|
||
|
||
XCTAssertEqual(viewModel.albumTotal, 1)
|
||
XCTAssertEqual(viewModel.albums.first?.name, "A")
|
||
}
|
||
|
||
func testPreShootRequiresSinglePrice() async {
|
||
let api = TravelAlbumMockAPI()
|
||
let viewModel = TravelAlbumEntryViewModel(currentScenicIdProvider: { 1 })
|
||
var message = ""
|
||
viewModel.onShowMessage = { message = $0 }
|
||
|
||
await viewModel.confirmCreate(mode: .preShoot, freeCount: "", singlePrice: "", packagePrice: "", order: nil, api: api)
|
||
|
||
XCTAssertEqual(message, "请输入单张照片价格")
|
||
XCTAssertEqual(api.createRequests.count, 0)
|
||
}
|
||
|
||
func testPreOrderRequiresOrder() async {
|
||
let api = TravelAlbumMockAPI()
|
||
let viewModel = TravelAlbumEntryViewModel(currentScenicIdProvider: { 1 })
|
||
var message = ""
|
||
viewModel.onShowMessage = { message = $0 }
|
||
|
||
await viewModel.confirmCreate(mode: .preOrder, freeCount: "", singlePrice: "", packagePrice: "", order: nil, api: api)
|
||
|
||
XCTAssertEqual(message, "请选择绑定订单")
|
||
}
|
||
|
||
func testCreateSuccessRefreshesAlbums() async {
|
||
let api = TravelAlbumMockAPI()
|
||
api.createResponse = TravelAlbumCreateResponse(id: 9)
|
||
api.listResponse = TravelAlbumListResponse(total: 1, list: [TravelAlbum(id: 9)])
|
||
let date = Calendar(identifier: .gregorian).date(from: DateComponents(year: 2026, month: 7, day: 7))!
|
||
let viewModel = TravelAlbumEntryViewModel(currentScenicIdProvider: { 1 }, dateProvider: { date })
|
||
|
||
await viewModel.confirmCreate(mode: .preShoot, freeCount: "2", singlePrice: "12.5", packagePrice: "88", order: nil, api: api)
|
||
|
||
XCTAssertEqual(api.createRequests.first?.name, "2026-07-07-001")
|
||
XCTAssertEqual(api.createRequests.first?.type, 1)
|
||
XCTAssertEqual(viewModel.albums.first?.id, 9)
|
||
XCTAssertFalse(viewModel.isCreateSheetVisible)
|
||
}
|
||
|
||
func testOrderAlreadyBoundReloadsOrders() async {
|
||
let api = TravelAlbumMockAPI()
|
||
api.createError = APIError.serverCode(100001, "订单已关联")
|
||
api.availableOrdersResponse = [TravelAlbumAvailableOrder(projectName: "新订单", orderNumber: "NO1")]
|
||
let viewModel = TravelAlbumEntryViewModel(currentScenicIdProvider: { 1 })
|
||
let order = TravelAlbumAvailableOrder(projectName: "项目", orderNumber: "NO0")
|
||
|
||
await viewModel.confirmCreate(mode: .preOrder, freeCount: "", singlePrice: "", packagePrice: "", order: order, api: api)
|
||
|
||
XCTAssertEqual(api.availableOrdersCallCount, 1)
|
||
XCTAssertEqual(viewModel.availableOrders.first?.projectName, "新订单")
|
||
}
|
||
}
|
||
|
||
@MainActor
|
||
/// 旅拍相册详情 ViewModel 测试。
|
||
final class TravelAlbumDetailViewModelTests: XCTestCase {
|
||
func testRefreshLoadsInfoAndMaterials() async {
|
||
let api = TravelAlbumMockAPI()
|
||
api.infoResponse = TravelAlbum(id: 2, name: "详情")
|
||
api.materialListResponses = [
|
||
TravelAlbumListResponse(total: 2, list: []),
|
||
TravelAlbumListResponse(total: 2, list: [TravelAlbumMaterial(id: 1), TravelAlbumMaterial(id: 2)]),
|
||
]
|
||
let viewModel = TravelAlbumDetailViewModel(albumId: 2)
|
||
|
||
await viewModel.refreshAll(api: api)
|
||
|
||
XCTAssertEqual(viewModel.album?.name, "详情")
|
||
XCTAssertEqual(viewModel.allPhotoCount, 2)
|
||
XCTAssertEqual(viewModel.materials.count, 2)
|
||
}
|
||
|
||
func testTabAndSortTriggerMaterialRequests() async {
|
||
let api = TravelAlbumMockAPI()
|
||
api.materialListResponses = [
|
||
TravelAlbumListResponse(total: 0, list: []),
|
||
TravelAlbumListResponse(total: 0, list: []),
|
||
]
|
||
let viewModel = TravelAlbumDetailViewModel(albumId: 2)
|
||
|
||
await viewModel.selectTab(.purchased, api: api)
|
||
await viewModel.setSortOption(.fileNameDesc, api: api)
|
||
|
||
XCTAssertEqual(api.materialRequests.first?.isPurchased, 1)
|
||
XCTAssertEqual(api.materialRequests.last?.orderBy, 4)
|
||
}
|
||
|
||
func testOnlyUnpurchasedMaterialCanBeSelected() {
|
||
let viewModel = TravelAlbumDetailViewModel(albumId: 2)
|
||
viewModel.toggleSelectionMode()
|
||
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 1, status: 2))
|
||
XCTAssertTrue(viewModel.selectedMaterialIds.isEmpty)
|
||
|
||
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 1, status: 1))
|
||
XCTAssertEqual(viewModel.selectedMaterialIds, [1])
|
||
}
|
||
|
||
func testDeleteAlbumCallsCallback() async {
|
||
let api = TravelAlbumMockAPI()
|
||
let viewModel = TravelAlbumDetailViewModel(albumId: 5)
|
||
var deletedId = 0
|
||
viewModel.onDeletedAlbum = { deletedId = $0 }
|
||
|
||
await viewModel.deleteAlbum(api: api)
|
||
|
||
XCTAssertEqual(api.deletedAlbumIds, [5])
|
||
XCTAssertEqual(deletedId, 5)
|
||
}
|
||
}
|
||
|
||
/// 有线传输 ViewModel 测试。
|
||
final class WiredCameraTransferViewModelTests: XCTestCase {
|
||
func testDefaultDisconnectedStateDoesNotDependOnBusinessServices() {
|
||
let viewModel = WiredCameraTransferViewModel(albumId: 1, albumTitle: "相册", headerPhone: "13800138000")
|
||
var message = ""
|
||
viewModel.onShowMessage = { message = $0 }
|
||
|
||
XCTAssertEqual(viewModel.cameraStatusText, "未连接 USB")
|
||
XCTAssertEqual(viewModel.tabCounts, [0, 0, 0])
|
||
viewModel.showUIOnlyMessage()
|
||
XCTAssertEqual(message, "仅同步 UI,暂未接入 OTG 传输")
|
||
}
|
||
}
|
||
|
||
@MainActor
|
||
/// 旅拍相册 API 测试替身。
|
||
final class TravelAlbumMockAPI: TravelAlbumServing {
|
||
struct MaterialRequest: Equatable {
|
||
let userEquityTravelId: Int
|
||
let page: Int
|
||
let pageSize: Int
|
||
let orderBy: Int
|
||
let isPurchased: Int?
|
||
}
|
||
|
||
var availableOrdersResponse: [TravelAlbumAvailableOrder] = []
|
||
var listResponse = TravelAlbumListResponse<TravelAlbum>()
|
||
var createResponse = TravelAlbumCreateResponse(id: 0)
|
||
var infoResponse = TravelAlbum()
|
||
var materialListResponses: [TravelAlbumListResponse<TravelAlbumMaterial>] = []
|
||
var mpCodeResponse = TravelAlbumMpCodeResponse(mpCodeOssUrl: "")
|
||
var createError: Error?
|
||
|
||
private(set) var availableOrdersCallCount = 0
|
||
private(set) var createRequests: [TravelAlbumCreateRequest] = []
|
||
private(set) var materialRequests: [MaterialRequest] = []
|
||
private(set) var deletedAlbumIds: [Int] = []
|
||
private(set) var deletedMaterialIds: [Int] = []
|
||
|
||
func availableOrders() async throws -> [TravelAlbumAvailableOrder] {
|
||
availableOrdersCallCount += 1
|
||
return availableOrdersResponse
|
||
}
|
||
|
||
func create(_ request: TravelAlbumCreateRequest) async throws -> TravelAlbumCreateResponse {
|
||
createRequests.append(request)
|
||
if let createError { throw createError }
|
||
return createResponse
|
||
}
|
||
|
||
func list(page: Int, pageSize: Int) async throws -> TravelAlbumListResponse<TravelAlbum> {
|
||
listResponse
|
||
}
|
||
|
||
func info(id: Int) async throws -> TravelAlbum {
|
||
infoResponse
|
||
}
|
||
|
||
func materialList(
|
||
userEquityTravelId: Int,
|
||
page: Int,
|
||
pageSize: Int,
|
||
orderBy: Int,
|
||
isPurchased: Int?
|
||
) async throws -> TravelAlbumListResponse<TravelAlbumMaterial> {
|
||
materialRequests.append(
|
||
MaterialRequest(
|
||
userEquityTravelId: userEquityTravelId,
|
||
page: page,
|
||
pageSize: pageSize,
|
||
orderBy: orderBy,
|
||
isPurchased: isPurchased
|
||
)
|
||
)
|
||
if materialListResponses.isEmpty { return TravelAlbumListResponse() }
|
||
return materialListResponses.removeFirst()
|
||
}
|
||
|
||
func deleteAlbum(id: Int) async throws {
|
||
deletedAlbumIds.append(id)
|
||
}
|
||
|
||
func deleteMaterial(id: Int) async throws {
|
||
deletedMaterialIds.append(id)
|
||
}
|
||
|
||
func mpCode(id: Int) async throws -> TravelAlbumMpCodeResponse {
|
||
mpCodeResponse
|
||
}
|
||
}
|