未绑定相册的新拍照片被错误过滤导致列表始终为空;同时调整入口卡片点击区域与详情素材网格布局。 Co-authored-by: Cursor <cursoragent@cursor.com>
157 lines
7.2 KiB
Swift
157 lines
7.2 KiB
Swift
//
|
|
// WiredCameraTransferViewModelTests.swift
|
|
// suixinkanTests
|
|
//
|
|
// Created by Codex on 2026/6/30.
|
|
//
|
|
|
|
import XCTest
|
|
@testable import suixinkan
|
|
|
|
@MainActor
|
|
/// 有线传图 ViewModel 测试。
|
|
final class WiredCameraTransferViewModelTests: XCTestCase {
|
|
/// 测试相机管道回调的首张新照片应出现在列表中。
|
|
func testFirstNewPipelineTaskAppearsInPhotos() async throws {
|
|
let camera = MockWiredCameraService()
|
|
let context = WiredTransferContext(
|
|
albumId: 1,
|
|
albumName: "测试相册",
|
|
phone: "",
|
|
orderNumber: ""
|
|
)
|
|
let viewModel = WiredCameraTransferViewModel(context: context, cameraService: camera)
|
|
let account = AccountContext()
|
|
account.applyLogin(profile: AccountProfile(userId: "101", displayName: "测试"))
|
|
|
|
await viewModel.start(
|
|
api: MockTravelAlbumAPIForWiredTransfer(),
|
|
ossService: WiredTransferMockOSSUploadService(),
|
|
scenicID: 1,
|
|
accountContext: account
|
|
)
|
|
|
|
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("DSC_NEW.JPG")
|
|
try Data(repeating: 0xFF, count: 16).write(to: tempURL)
|
|
defer { try? FileManager.default.removeItem(at: tempURL) }
|
|
|
|
let asset = CameraAsset(id: "ptp_new_001", filename: "DSC_NEW.JPG", fileSize: 16)
|
|
camera.downloadURL = tempURL
|
|
camera.onNewAsset?(asset)
|
|
try await Task.sleep(nanoseconds: 500_000_000)
|
|
|
|
XCTAssertEqual(viewModel.photos.count, 1)
|
|
XCTAssertEqual(viewModel.photos.first?.id, "ptp_new_001")
|
|
XCTAssertEqual(viewModel.photos.first?.fileName, "DSC_NEW.JPG")
|
|
}
|
|
|
|
/// 测试绑定到其他相册的照片不会出现在当前相册列表。
|
|
func testPhotoBoundToOtherAlbumIsFilteredOut() async throws {
|
|
let bindingStore = WiredTransferPhotoStore(
|
|
accountPrefixProvider: { "user_101" },
|
|
userIDProvider: { "101" }
|
|
)
|
|
bindingStore.bindPhotoToAlbum(photoID: "ptp_other_album", albumID: 99)
|
|
defer { bindingStore.bindPhotoToAlbum(photoID: "ptp_other_album", albumID: 1) }
|
|
|
|
let camera = MockWiredCameraService()
|
|
let context = WiredTransferContext(
|
|
albumId: 1,
|
|
albumName: "当前相册",
|
|
phone: "",
|
|
orderNumber: ""
|
|
)
|
|
let viewModel = WiredCameraTransferViewModel(context: context, cameraService: camera)
|
|
let account = AccountContext()
|
|
account.applyLogin(profile: AccountProfile(userId: "101", displayName: "测试"), accountType: "user")
|
|
|
|
await viewModel.start(
|
|
api: MockTravelAlbumAPIForWiredTransfer(),
|
|
ossService: WiredTransferMockOSSUploadService(),
|
|
scenicID: 1,
|
|
accountContext: account
|
|
)
|
|
|
|
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("DSC_OTHER.JPG")
|
|
try Data(repeating: 0xAA, count: 8).write(to: tempURL)
|
|
defer { try? FileManager.default.removeItem(at: tempURL) }
|
|
|
|
camera.downloadURL = tempURL
|
|
camera.onNewAsset?(CameraAsset(id: "ptp_other_album", filename: "DSC_OTHER.JPG", fileSize: 8))
|
|
try await Task.sleep(nanoseconds: 500_000_000)
|
|
|
|
XCTAssertTrue(viewModel.photos.isEmpty)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private final class MockWiredCameraService: CameraServiceProtocol {
|
|
let brand: CameraBrand = .sony
|
|
var connectionState: CameraConnectionState = .connected(deviceName: "Mock")
|
|
var onConnectionStateChange: ((CameraConnectionState) -> Void)?
|
|
var onNewAsset: ((CameraAsset) -> Void)?
|
|
var downloadURL: URL?
|
|
|
|
func connect() async {}
|
|
func disconnect() async {}
|
|
|
|
func listAssets() async throws -> [CameraAsset] {
|
|
[]
|
|
}
|
|
|
|
func downloadAsset(_ asset: CameraAsset) async throws -> URL {
|
|
if let downloadURL {
|
|
return downloadURL
|
|
}
|
|
let url = FileManager.default.temporaryDirectory.appendingPathComponent(asset.filename)
|
|
if !FileManager.default.fileExists(atPath: url.path) {
|
|
try Data(repeating: 0xFF, count: 8).write(to: url)
|
|
}
|
|
return url
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private final class MockTravelAlbumAPIForWiredTransfer: TravelAlbumServing {
|
|
func availableOrders() async throws -> [TravelAlbumAvailableOrder] { [] }
|
|
func createAlbum(_ request: TravelAlbumCreateRequest) async throws -> TravelAlbumCreateResponse {
|
|
TravelAlbumCreateResponse(id: 1)
|
|
}
|
|
func albumList(page: Int, pageSize: Int) async throws -> ListPayload<TravelAlbumItem> {
|
|
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 {
|
|
let data = Data(
|
|
#"{"code":100000,"msg":"success","data":{"id":"11","user_equity_travel_id":"8","status":"1","order_number":"","user_id":"100","file_name":"DSC_001.JPG","file_type":"2","file_url":"https://cdn/a.jpg","file_size":"2048","cover_url":"","is_purchased":"0","created_at":"2026","updated_at":"2026"}}"#.utf8
|
|
)
|
|
let envelope = try JSONDecoder().decode(APIEnvelope<TravelAlbumMaterial>.self, from: data)
|
|
return try XCTUnwrap(envelope.data)
|
|
}
|
|
func deleteMaterial(_ request: TravelAlbumDeleteMaterialRequest) async throws {}
|
|
func mpCode(id: Int) async throws -> TravelAlbumMpCodeResponse {
|
|
TravelAlbumMpCodeResponse(mpCodeOssURL: "")
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private final class WiredTransferMockOSSUploadService: OSSUploadServing {
|
|
func uploadUserAvatar(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String { "" }
|
|
func uploadRealNameImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String { "" }
|
|
func uploadCloudFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String { "" }
|
|
func uploadAlbumFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
|
onProgress(100)
|
|
return "https://cdn/mock/\(fileName)"
|
|
}
|
|
func uploadTaskFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String { "" }
|
|
func uploadProjectImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String { "" }
|
|
func uploadPunchPointImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String { "" }
|
|
func uploadScenicApplicationImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String { "" }
|
|
func uploadBankCardImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String { "" }
|
|
}
|