feat: add iOS travel album photo preview
This commit is contained in:
@@ -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))!
|
||||
|
||||
Reference in New Issue
Block a user