feat: add iOS travel album photo preview

This commit is contained in:
2026-08-10 10:07:19 +08:00
parent dcdab3cff7
commit 99fbad3e97
5 changed files with 1101 additions and 2 deletions
+122
View File
@@ -8,6 +8,109 @@ import XCTest
/// 旅拍相册模型与展示工具测试。
final class TravelAlbumModelsTests: XCTestCase {
func testPreviewProjectMapsMaterialToOriginalAsset() {
let material = TravelAlbumMaterial(
id: 8,
fileName: "IMG_0008.JPG",
fileUrl: "https://cdn.example.com/original.jpg",
fileSize: 4096,
coverUrl: "https://cdn.example.com/cover.jpg"
)
let project = TravelAlbumPreviewProject(material: material)
XCTAssertEqual(project.originalMaterialId, 8)
XCTAssertEqual(project.orderedAssets.map(\.kind), [.original])
XCTAssertEqual(project.orderedAssets.first?.displayURL, material.fileUrl)
XCTAssertEqual(project.orderedAssets.first?.previewURL, material.coverUrl)
XCTAssertFalse(project.hasVariants)
}
func testPreviewAssetFallsBackToAvailableURLForBothQualityLevels() {
let missingOriginal = TravelAlbumPreviewAsset(
id: "cover-only",
kind: .cover,
fileURL: " ",
coverURL: "https://cdn.example.com/cover.jpg",
fileName: "cover.jpg",
fileSize: 100
)
let missingCover = TravelAlbumPreviewAsset(
id: "original-only",
kind: .original,
fileURL: "https://cdn.example.com/original.jpg",
coverURL: "\n",
fileName: "original.jpg",
fileSize: 200
)
XCTAssertEqual(missingOriginal.displayURL, missingOriginal.coverURL)
XCTAssertEqual(missingOriginal.previewURL, missingOriginal.coverURL)
XCTAssertEqual(missingCover.displayURL, missingCover.fileURL)
XCTAssertEqual(missingCover.previewURL, missingCover.fileURL)
}
func testPreviewProjectKeepsCanonicalExistingVariantOrder() {
let project = makePreviewProject(kinds: [.cover, .original, .atmosphere])
XCTAssertEqual(project.orderedAssets.map(\.kind), [.original, .atmosphere, .cover])
XCTAssertTrue(project.hasVariants)
}
func testPreviewNavigatorBuildsNodesForBothModes() {
let projects = [
makePreviewProject(id: 1, kinds: [.original, .retouched]),
makePreviewProject(id: 2, kinds: [.original, .cover]),
]
XCTAssertEqual(
TravelAlbumPreviewNavigator.nodes(projects: projects, mode: .projectsOnly),
[
TravelAlbumPreviewNode(projectIndex: 0, kind: .original),
TravelAlbumPreviewNode(projectIndex: 1, kind: .original),
]
)
XCTAssertEqual(
TravelAlbumPreviewNavigator.nodes(projects: projects, mode: .includeVariants).map(\.kind),
[.original, .retouched, .original, .cover]
)
}
func testPreviewNavigatorBackwardFromOriginalTargetsPreviousOriginal() {
let projects = [
makePreviewProject(id: 1, kinds: [.original, .retouched, .cover]),
makePreviewProject(id: 2, kinds: [.original, .atmosphere]),
]
let nodes = TravelAlbumPreviewNavigator.nodes(projects: projects, mode: .includeVariants)
let secondOriginal = nodes.firstIndex {
$0.projectIndex == 1 && $0.kind == .original
}!
let target = TravelAlbumPreviewNavigator.backwardTargetIndex(nodes: nodes, currentIndex: secondOriginal)
XCTAssertEqual(nodes[target], TravelAlbumPreviewNode(projectIndex: 0, kind: .original))
}
func testPlaceholderPreviewActionsReturnUnavailableWithoutMutation() async {
let handler = PlaceholderTravelAlbumPreviewActionHandler()
let aiResult = await handler.requestAIRetouch(originalMaterialId: 9)
let deleteResult = await handler.deleteProject(originalMaterialId: 9)
let refreshResult = await handler.refreshProject(originalMaterialId: 9)
XCTAssertEqual(
aiResult,
.unavailable("AI修复功能待接口接入")
)
XCTAssertEqual(
deleteResult,
.unavailable("项目删除接口待接入")
)
XCTAssertEqual(
refreshResult,
.unavailable("关联图片刷新接口待接入")
)
}
func testTravelAlbumDecodesSnakeCaseFields() throws {
let json = """
{
@@ -68,6 +171,25 @@ final class TravelAlbumModelsTests: XCTestCase {
XCTAssertEqual(TravelAlbumDisplayFormatter.fileSizeText(2 * 1024 * 1024), "2.0MB")
}
private func makePreviewProject(
id: Int = 1,
kinds: [TravelAlbumPreviewAssetKind]
) -> TravelAlbumPreviewProject {
TravelAlbumPreviewProject(
originalMaterialId: id,
assets: kinds.map { kind in
TravelAlbumPreviewAsset(
id: "\(id)-\(kind.rawValue)",
kind: kind,
fileURL: "https://cdn.example.com/\(id)-\(kind.rawValue).jpg",
coverURL: "",
fileName: "\(kind.title).jpg",
fileSize: 1024
)
}
)
}
@MainActor
func testBuildTodayAlbumNameUsesExistingSameDayCount() {
let date = Calendar(identifier: .gregorian).date(from: DateComponents(year: 2026, month: 7, day: 7))!