Add TravelAlbum, ProfileSpace, and wired camera transfer modules.

Introduce travel album entry with Sony PTP tethering pipeline, profile space settings page, home routing updates, Launch Screen storyboard, and related tests/docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-30 09:41:05 +08:00
parent 5692134efc
commit d2fe5d71e4
48 changed files with 6477 additions and 11 deletions

View File

@ -0,0 +1,124 @@
//
// TravelAlbumViewModelTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/29.
//
import XCTest
@testable import suixinkan
@MainActor
/// ViewModel
final class TravelAlbumViewModelTests: XCTestCase {
///
func testCreatePreShootRequiresSinglePrice() async {
let viewModel = TravelAlbumEntryViewModel()
let api = MockTravelAlbumAPI()
let success = await viewModel.confirmCreateTask(
api: api,
mode: TravelAlbumEntryViewModel.modePreShoot,
freeCount: "3",
singlePrice: "",
packagePrice: "99",
order: nil,
scenicSelected: true
)
XCTAssertFalse(success)
XCTAssertEqual(viewModel.errorMessage, "请输入单张照片价格")
XCTAssertEqual(api.createCallCount, 0)
}
///
func testCreatePreOrderRequiresOrder() async {
let viewModel = TravelAlbumEntryViewModel()
let api = MockTravelAlbumAPI()
let success = await viewModel.confirmCreateTask(
api: api,
mode: TravelAlbumEntryViewModel.modePreOrder,
freeCount: "",
singlePrice: "",
packagePrice: "",
order: nil,
scenicSelected: true
)
XCTAssertFalse(success)
XCTAssertEqual(viewModel.errorMessage, "请选择绑定订单")
}
///
func testCreateSuccessReloadsList() async {
let viewModel = TravelAlbumEntryViewModel()
let api = MockTravelAlbumAPI()
let success = await viewModel.confirmCreateTask(
api: api,
mode: TravelAlbumEntryViewModel.modePreShoot,
freeCount: "3",
singlePrice: "9.9",
packagePrice: "99",
order: nil,
scenicSelected: true
)
XCTAssertTrue(success)
XCTAssertEqual(api.createCallCount, 1)
XCTAssertEqual(api.listCallCount, 1)
}
///
func testBuildTodayAlbumNameIncrementsSuffix() {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let today = formatter.string(from: Date())
let name = TravelAlbumEntryViewModel.buildTodayAlbumName(
existingNames: ["\(today)-001", "\(today)-002", "2026-06-28-001"]
)
XCTAssertTrue(name.hasSuffix("003"))
}
}
@MainActor
private final class MockTravelAlbumAPI: TravelAlbumServing {
var createCallCount = 0
var listCallCount = 0
func availableOrders() async throws -> [TravelAlbumAvailableOrder] { [] }
func createAlbum(_ request: TravelAlbumCreateRequest) async throws -> TravelAlbumCreateResponse {
createCallCount += 1
return TravelAlbumCreateResponse(id: 9)
}
func albumList(page: Int, pageSize: Int) async throws -> ListPayload<TravelAlbumItem> {
listCallCount += 1
return ListPayload(total: 0, list: [])
}
func albumInfo(id: Int) async throws -> TravelAlbumItem {
throw APIError.emptyData
}
func editAlbum(_ request: TravelAlbumEditRequest) async throws {}
func deleteAlbum(_ request: TravelAlbumDeleteRequest) async throws {}
func materialList(userEquityTravelId: Int, page: Int, pageSize: Int) async throws -> ListPayload<TravelAlbumMaterial> {
ListPayload(total: 0, list: [])
}
func uploadMaterial(_ request: TravelAlbumUploadMaterialRequest) async throws -> TravelAlbumMaterial {
throw APIError.emptyData
}
func deleteMaterial(_ request: TravelAlbumDeleteMaterialRequest) async throws {}
func mpCode(id: Int) async throws -> TravelAlbumMpCodeResponse {
TravelAlbumMpCodeResponse(mpCodeOssURL: "")
}
}