feat: add AI retouch before-after comparison

This commit is contained in:
2026-08-13 17:46:06 +08:00
parent e190b5958b
commit 0f3b26991e
12 changed files with 953 additions and 145 deletions
+105
View File
@@ -8,6 +8,40 @@ import XCTest
/// 旅拍相册模型与展示工具测试。
final class TravelAlbumModelsTests: XCTestCase {
func testBeforeAfterComparisonMotionMatchesShipSwiftInteractionRange() {
XCTAssertEqual(BeforeAfterComparisonMotion.automaticFraction(elapsed: 0), 0.5, accuracy: 0.001)
XCTAssertEqual(
BeforeAfterComparisonMotion.automaticFraction(
elapsed: .pi / (2 * BeforeAfterComparisonMotion.automaticSpeed)
),
0.8,
accuracy: 0.001
)
XCTAssertEqual(BeforeAfterComparisonMotion.clampedManualFraction(0), 0.05)
XCTAssertEqual(BeforeAfterComparisonMotion.clampedManualFraction(1), 0.95)
XCTAssertEqual(BeforeAfterComparisonMotion.clampedManualFraction(0.42), 0.42)
}
func testBeforeAfterComparisonLayoutFitsVariableRatiosWithoutCropping() {
let landscape = BeforeAfterComparisonLayout.fittedSize(
aspectRatio: 16.0 / 9.0,
maximumSize: CGSize(width: 358, height: 580)
)
let portrait = BeforeAfterComparisonLayout.fittedSize(
aspectRatio: 2.0 / 3.0,
maximumSize: CGSize(width: 358, height: 580)
)
XCTAssertEqual(landscape.width, 358, accuracy: 0.001)
XCTAssertEqual(landscape.height, 201.375, accuracy: 0.001)
XCTAssertEqual(portrait.width, 358, accuracy: 0.001)
XCTAssertEqual(portrait.height, 537, accuracy: 0.001)
XCTAssertEqual(
BeforeAfterComparisonLayout.aspectRatio(for: CGSize(width: 4000, height: 3000)),
4.0 / 3.0
)
}
func testPreviewProjectMapsMaterialToOriginalAsset() {
let material = TravelAlbumMaterial(
id: 8,
@@ -68,6 +102,77 @@ final class TravelAlbumModelsTests: XCTestCase {
)
}
func testPreviewProjectBuildsComparisonOnlyForValidAIResults() {
let project = makePreviewProject(id: 8, kinds: [.original, .retouched, .atmosphere, .cover])
XCTAssertEqual(
project.comparisonContent(for: .retouched),
BeforeAfterComparisonContent(
beforeURL: "https://cdn.example.com/8-0.jpg",
afterURL: "https://cdn.example.com/8-1.jpg"
)
)
XCTAssertNotNil(project.comparisonContent(for: .atmosphere))
XCTAssertNil(project.comparisonContent(for: .original))
XCTAssertNil(project.comparisonContent(for: .cover))
let missingResultURL = TravelAlbumPreviewProject(
originalMaterialId: 9,
assets: [
TravelAlbumPreviewAsset(
id: "original-9",
kind: .original,
fileURL: "https://cdn.example.com/original.jpg",
coverURL: "",
fileName: "原图.jpg",
fileSize: 1
),
TravelAlbumPreviewAsset(
id: "retouched-9",
kind: .retouched,
fileURL: " ",
coverURL: "",
fileName: "精修.jpg",
fileSize: 1
),
]
)
XCTAssertNil(missingResultURL.comparisonContent(for: .retouched))
}
func testTemplateComparisonContentRequiresBothURLs() {
let valid = TravelAlbumAIRetouchTemplate(
id: 1,
name: "清透",
previewURL: "https://cdn.example.com/preview.jpg",
beforeURL: " https://cdn.example.com/before.jpg ",
afterURL: "https://cdn.example.com/after.jpg"
)
let missingAfter = TravelAlbumAIRetouchTemplate(
id: 2,
name: "暖阳",
previewURL: "https://cdn.example.com/preview.jpg",
beforeURL: "https://cdn.example.com/before.jpg"
)
XCTAssertEqual(valid.comparisonContent?.beforeURL, "https://cdn.example.com/before.jpg")
XCTAssertEqual(valid.comparisonContent?.afterLabel, "效果图")
XCTAssertNil(missingAfter.comparisonContent)
}
func testTemplateDecodingDefaultsMissingComparisonURLsToEmptyStrings() throws {
let data = Data(
#"{"id":3,"name":"旧模板","preview_url":"https://cdn.example.com/preview.jpg"}"#.utf8
)
let template = try JSONDecoder().decode(TravelAlbumAIRetouchTemplate.self, from: data)
XCTAssertEqual(template.previewURL, "https://cdn.example.com/preview.jpg")
XCTAssertEqual(template.beforeURL, "")
XCTAssertEqual(template.afterURL, "")
XCTAssertNil(template.comparisonContent)
}
func testPreviewAssetFallsBackToAvailableURLForBothQualityLevels() {
let missingOriginal = TravelAlbumPreviewAsset(
id: "cover-only",