对齐有线传图页 Android UI,并改用相对路径持久化本地照片。
还原顶栏、浮动卡片、时间侧栏与底部交互;照片路径改为 CameraDownloads 相对路径并兼容旧数据;格式 Chip 固定 JPG 且不可点击。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,47 @@
|
||||
//
|
||||
// CameraDownloadStorageTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
/// CameraDownloadStorage 相对路径持久化测试。
|
||||
final class CameraDownloadStorageTests: XCTestCase {
|
||||
/// 测试相对路径前缀。
|
||||
func testRelativePathUsesCameraDownloadsPrefix() {
|
||||
let fileURL = CameraDownloadStorage.downloadsDirectory.appendingPathComponent("1730_test.JPG")
|
||||
XCTAssertEqual(CameraDownloadStorage.relativePath(for: fileURL), "CameraDownloads/1730_test.JPG")
|
||||
}
|
||||
|
||||
/// 测试相对路径解析。
|
||||
func testResolveLocalURLFromRelativePath() {
|
||||
let stored = "CameraDownloads/1730_test.JPG"
|
||||
let resolved = CameraDownloadStorage.resolveLocalURL(from: stored)
|
||||
let expected = CameraDownloadStorage.documentsDirectory.appendingPathComponent(stored)
|
||||
XCTAssertEqual(resolved, expected)
|
||||
}
|
||||
|
||||
/// 测试旧版绝对路径迁移。
|
||||
func testMigrateStoredPathFromLegacyAbsolutePath() throws {
|
||||
let fileURL = CameraDownloadStorage.uniqueLocalURL(for: "legacy.jpg")
|
||||
try Data("jpeg".utf8).write(to: fileURL)
|
||||
defer { try? FileManager.default.removeItem(at: fileURL) }
|
||||
|
||||
let migrated = CameraDownloadStorage.migrateStoredPath(fileURL.path)
|
||||
XCTAssertEqual(migrated, "CameraDownloads/\(fileURL.lastPathComponent)")
|
||||
XCTAssertEqual(CameraDownloadStorage.resolveLocalURL(from: migrated ?? "")?.path, fileURL.path)
|
||||
}
|
||||
|
||||
/// 测试预览 URL 字符串生成。
|
||||
func testPreviewURLStringFromRelativePath() throws {
|
||||
let fileURL = CameraDownloadStorage.uniqueLocalURL(for: "preview.jpg")
|
||||
try Data("jpeg".utf8).write(to: fileURL)
|
||||
defer { try? FileManager.default.removeItem(at: fileURL) }
|
||||
|
||||
let stored = CameraDownloadStorage.relativePath(for: fileURL)
|
||||
let preview = CameraDownloadStorage.previewURLString(from: stored)
|
||||
XCTAssertFalse(preview.isEmpty)
|
||||
XCTAssertTrue(preview.hasPrefix("file://"))
|
||||
}
|
||||
}
|
||||
@ -11,13 +11,16 @@ import XCTest
|
||||
@MainActor
|
||||
/// 相机传输管道测试,使用 Mock 相机与上传 Sink。
|
||||
final class CameraTransferPipelineTests: XCTestCase {
|
||||
/// 测试新照片下载后触发上传 Sink。
|
||||
/// 测试新照片下载后触发上传 Sink,并持久化相对路径。
|
||||
func testNewAssetTriggersUploadSink() async {
|
||||
let camera = MockCameraService()
|
||||
let sink = MockUploadSink()
|
||||
let pipeline = CameraTransferPipeline(cameraService: camera)
|
||||
pipeline.configure(uploadSink: sink, uploadEnabled: true)
|
||||
|
||||
var latestTasks: [CameraTransferTask] = []
|
||||
pipeline.onTasksUpdated = { latestTasks = $0 }
|
||||
|
||||
let asset = CameraAsset(id: "ptp_1", filename: "DSC_001.JPG", fileSize: 1024)
|
||||
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("DSC_001.JPG")
|
||||
try? Data(repeating: 0xFF, count: 8).write(to: tempURL)
|
||||
@ -26,6 +29,12 @@ final class CameraTransferPipelineTests: XCTestCase {
|
||||
try? await Task.sleep(nanoseconds: 300_000_000)
|
||||
|
||||
XCTAssertEqual(sink.uploadCount, 1)
|
||||
let savedTask = latestTasks.first { $0.assetID == asset.id }
|
||||
XCTAssertEqual(savedTask?.localPath?.hasPrefix("CameraDownloads/"), true)
|
||||
XCTAssertEqual(savedTask?.localPath?.hasPrefix("/"), false)
|
||||
if let localURL = savedTask?.localURL {
|
||||
try? FileManager.default.removeItem(at: localURL)
|
||||
}
|
||||
try? FileManager.default.removeItem(at: tempURL)
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +82,93 @@ final class WiredCameraTransferViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertTrue(viewModel.photos.isEmpty)
|
||||
}
|
||||
|
||||
/// 测试批量上传按钮三态流转。
|
||||
func testBatchUploadButtonThreeStateFlow() async {
|
||||
let viewModel = makeViewModelWithPendingPhotos()
|
||||
let api = MockTravelAlbumAPIForWiredTransfer()
|
||||
let oss = WiredTransferMockOSSUploadService()
|
||||
|
||||
XCTAssertFalse(viewModel.selectUploadMode)
|
||||
await viewModel.onBatchUploadButtonClick(api: api, ossService: oss, scenicID: 1)
|
||||
XCTAssertTrue(viewModel.selectUploadMode)
|
||||
XCTAssertTrue(viewModel.selectedPhotoIDs.isEmpty)
|
||||
|
||||
viewModel.togglePhotoSelection(id: "pending-1")
|
||||
XCTAssertEqual(viewModel.selectedPhotoIDs, ["pending-1"])
|
||||
|
||||
await viewModel.onBatchUploadButtonClick(api: api, ossService: oss, scenicID: 1)
|
||||
XCTAssertFalse(viewModel.selectUploadMode)
|
||||
XCTAssertTrue(viewModel.selectedPhotoIDs.isEmpty)
|
||||
}
|
||||
|
||||
/// 测试全选 pending 与取消全选。
|
||||
func testToggleSelectAllPendingInVisible() {
|
||||
let viewModel = makeViewModelWithPendingPhotos()
|
||||
viewModel.selectUploadMode = true
|
||||
|
||||
viewModel.toggleSelectAllPendingInVisible()
|
||||
XCTAssertEqual(viewModel.selectedPhotoIDs, ["pending-1", "pending-2"])
|
||||
|
||||
viewModel.toggleSelectAllPendingInVisible()
|
||||
XCTAssertTrue(viewModel.selectedPhotoIDs.isEmpty)
|
||||
}
|
||||
|
||||
/// 测试指定上传今日拍摄筛选。
|
||||
func testUploadAllTodayCapturedFiltersByDate() async {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
formatter.locale = Locale(identifier: "zh_CN")
|
||||
let today = formatter.string(from: Date())
|
||||
|
||||
let viewModel = WiredCameraTransferViewModel(
|
||||
context: WiredTransferContext(albumId: 1, albumName: "相册", phone: "", orderNumber: ""),
|
||||
cameraService: MockWiredCameraService()
|
||||
)
|
||||
viewModel.photos = [
|
||||
makePendingPhoto(id: "today", capturedAt: today),
|
||||
makePendingPhoto(id: "old", capturedAt: "2020-01-01 09:00:00"),
|
||||
]
|
||||
|
||||
await viewModel.onSpecifyUploadOptionSelected(
|
||||
WiredCameraTransferViewModel.specifyUploadTodayCaptured,
|
||||
api: MockTravelAlbumAPIForWiredTransfer(),
|
||||
ossService: WiredTransferMockOSSUploadService(),
|
||||
scenicID: 1
|
||||
)
|
||||
XCTAssertNil(viewModel.errorMessage)
|
||||
}
|
||||
|
||||
private func makeViewModelWithPendingPhotos() -> WiredCameraTransferViewModel {
|
||||
let viewModel = WiredCameraTransferViewModel(
|
||||
context: WiredTransferContext(albumId: 1, albumName: "相册", phone: "", orderNumber: ""),
|
||||
cameraService: MockWiredCameraService()
|
||||
)
|
||||
viewModel.photos = [
|
||||
makePendingPhoto(id: "pending-1", capturedAt: "2026-05-20 12:05:18"),
|
||||
makePendingPhoto(id: "pending-2", capturedAt: "2026-05-20 12:18:06"),
|
||||
WiredTransferPhotoItem(
|
||||
id: "uploaded-1",
|
||||
fileName: "uploaded.JPG",
|
||||
thumbnailURL: "",
|
||||
capturedAt: "2026-05-20 12:20:00",
|
||||
fileSizeText: "1 MB",
|
||||
status: .uploaded
|
||||
),
|
||||
]
|
||||
return viewModel
|
||||
}
|
||||
|
||||
private func makePendingPhoto(id: String, capturedAt: String) -> WiredTransferPhotoItem {
|
||||
WiredTransferPhotoItem(
|
||||
id: id,
|
||||
fileName: "\(id).JPG",
|
||||
thumbnailURL: "",
|
||||
capturedAt: capturedAt,
|
||||
fileSizeText: "1 MB",
|
||||
status: .pending
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
//
|
||||
// WiredTransferPhotoGroupingTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
/// 有线传图照片分组逻辑测试。
|
||||
final class WiredTransferPhotoGroupingTests: XCTestCase {
|
||||
/// 测试 30 分钟时间段分组与侧栏构建。
|
||||
func testBuildPhotoSectionsAndSidebarGroups() {
|
||||
let photos = [
|
||||
makePhoto(id: "1", capturedAt: "2026-05-20 12:05:18", status: .uploaded),
|
||||
makePhoto(id: "2", capturedAt: "2026-05-20 12:18:06", status: .pending),
|
||||
makePhoto(id: "3", capturedAt: "2026-05-20 11:55:51", status: .failed),
|
||||
]
|
||||
|
||||
let sections = photos.buildPhotoSections()
|
||||
XCTAssertEqual(sections.count, 2)
|
||||
XCTAssertEqual(sections[0].photos.count, 2)
|
||||
XCTAssertTrue(sections[0].headerTitle.contains("12:00 - 12:30"))
|
||||
|
||||
let sidebar = photos.buildSidebarGroups()
|
||||
XCTAssertEqual(sidebar.count, 1)
|
||||
XCTAssertEqual(sidebar[0].slots.count, 2)
|
||||
XCTAssertEqual(sidebar[0].slots[0].photoCount, 2)
|
||||
}
|
||||
|
||||
/// 测试今日拍摄筛选。
|
||||
func testIsCapturedTodayUsesLocalCalendar() {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
formatter.locale = Locale(identifier: "zh_CN")
|
||||
let today = formatter.string(from: Date())
|
||||
|
||||
let photo = makePhoto(id: "today", capturedAt: today, status: .pending)
|
||||
XCTAssertTrue(photo.isCapturedToday())
|
||||
XCTAssertFalse(makePhoto(id: "old", capturedAt: "2020-01-01 10:00:00", status: .pending).isCapturedToday())
|
||||
}
|
||||
|
||||
/// 测试 Tab 计数与 pending 可选集合。
|
||||
func testTransferTabCountsAndSelectablePendingIDs() {
|
||||
let photos = [
|
||||
makePhoto(id: "1", capturedAt: "2026-05-20 12:05:18", status: .uploaded),
|
||||
makePhoto(id: "2", capturedAt: "2026-05-20 12:18:06", status: .pending),
|
||||
makePhoto(id: "3", capturedAt: "2026-05-20 12:35:44", status: .failed),
|
||||
]
|
||||
XCTAssertEqual(photos.transferTabCounts, [3, 1, 1])
|
||||
XCTAssertEqual(photos.selectablePendingPhotoIDs, ["2"])
|
||||
}
|
||||
|
||||
private func makePhoto(
|
||||
id: String,
|
||||
capturedAt: String,
|
||||
status: WiredTransferUploadStatus
|
||||
) -> WiredTransferPhotoItem {
|
||||
WiredTransferPhotoItem(
|
||||
id: id,
|
||||
fileName: "\(id).JPG",
|
||||
thumbnailURL: "",
|
||||
capturedAt: capturedAt,
|
||||
fileSizeText: "1.00 MB",
|
||||
resolutionText: "4032x3024",
|
||||
status: status
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user