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
@@ -637,35 +637,96 @@ final class TravelAlbumDetailViewControllerTests: XCTestCase {
XCTAssertEqual(cell.accessibilityValue, "未选择")
}
func testAIRetouchTemplatePreviewUsesImmersiveZoomableLayout() throws {
let template = TravelAlbumAIRetouchTemplate(
id: 2,
name: "简约留白",
previewURL: ""
func testBeforeAfterComparisonUsesImmersiveAccessibleLayout() throws {
let controller = BeforeAfterComparisonViewController(
viewModel: BeforeAfterComparisonViewModel(
content: BeforeAfterComparisonContent(
beforeURL: "https://cdn.example.com/before.jpg",
afterURL: "https://cdn.example.com/after.jpg"
)
)
)
let controller = TravelAlbumAIRetouchTemplatePreviewViewController(template: template)
controller.loadViewIfNeeded()
controller.view.frame = CGRect(x: 0, y: 0, width: 390, height: 844)
controller.view.layoutIfNeeded()
let scrollView = try XCTUnwrap(
let imageContainer = try XCTUnwrap(
controller.view.findSubview {
$0.accessibilityIdentifier == "travelAlbum.aiRetouchTemplatePreviewScrollView"
} as? UIScrollView
$0.accessibilityIdentifier == "travelAlbum.beforeAfterImageContainer"
}
)
let backButton = try XCTUnwrap(
controller.view.findSubview {
$0.accessibilityIdentifier == "travelAlbum.aiRetouchTemplatePreviewBackButton"
$0.accessibilityIdentifier == "travelAlbum.beforeAfterBackButton"
} as? UIButton
)
let divider = try XCTUnwrap(
controller.view.findSubview {
$0.accessibilityIdentifier == "travelAlbum.beforeAfterDivider"
}
)
XCTAssertEqual(controller.view.backgroundColor, UIColor(hex: 0x05070D))
XCTAssertTrue(controller.view.allLabels().contains { $0.text == "原图" })
XCTAssertTrue(controller.view.allLabels().contains { $0.text == "效果图" })
XCTAssertEqual(imageContainer.layer.cornerRadius, 0)
XCTAssertTrue(divider.accessibilityTraits.contains(.adjustable))
XCTAssertEqual(divider.accessibilityValue, "原图占比 50%")
XCTAssertGreaterThanOrEqual(backButton.bounds.width, 44)
XCTAssertGreaterThanOrEqual(backButton.bounds.height, 44)
}
func testPreviewComparisonButtonOnlyShowsForSelectedAIResult() throws {
UIView.setAnimationsEnabled(false)
defer { UIView.setAnimationsEnabled(true) }
let project = TravelAlbumPreviewProject(
originalMaterialId: 7,
assets: [
TravelAlbumPreviewAsset(
id: "original-7",
kind: .original,
fileURL: "https://cdn.example.com/original.jpg",
coverURL: "",
fileName: "原图.jpg",
fileSize: 10
),
TravelAlbumPreviewAsset(
id: "retouched-7",
kind: .retouched,
fileURL: "https://cdn.example.com/retouched.jpg",
coverURL: "",
fileName: "精修.jpg",
fileSize: 10
),
]
)
let controller = TravelAlbumPhotoPreviewViewController(
projects: [project],
totalCount: 1,
startProjectIndex: 0
)
controller.loadViewIfNeeded()
controller.view.frame = CGRect(x: 0, y: 0, width: 390, height: 844)
controller.view.layoutIfNeeded()
let segmentedControl = try XCTUnwrap(
controller.view.findSubview {
$0.accessibilityIdentifier == "travelAlbum.previewVariantSegmentedControl"
} as? UISegmentedControl
)
let comparisonButton = try XCTUnwrap(
controller.view.findSubview {
$0.accessibilityIdentifier == "travelAlbum.previewComparisonButton"
} as? UIButton
)
XCTAssertEqual(controller.view.backgroundColor, .black)
XCTAssertTrue(controller.view.allLabels().contains { $0.text == "简约留白" })
XCTAssertTrue(controller.view.allLabels().contains { $0.text == "双指缩放查看细节" })
XCTAssertEqual(scrollView.minimumZoomScale, 1)
XCTAssertEqual(scrollView.maximumZoomScale, 4)
XCTAssertGreaterThanOrEqual(backButton.bounds.width, 44)
XCTAssertGreaterThanOrEqual(backButton.bounds.height, 44)
XCTAssertTrue(comparisonButton.isHidden)
segmentedControl.selectedSegmentIndex = 1
segmentedControl.sendActions(for: .valueChanged)
XCTAssertFalse(comparisonButton.isHidden)
XCTAssertNil(comparisonButton.configuration?.title)
XCTAssertNotNil(comparisonButton.configuration?.image)
XCTAssertEqual(comparisonButton.bounds.size, CGSize(width: 48, height: 48))
}
func testPreviewAIRetouchUsesSelectedRetouchedTabWorkflowAndStaysPresented() async throws {