feat: add AI retouch and album preview actions
This commit is contained in:
@@ -26,6 +26,46 @@ final class TravelAlbumModelsTests: XCTestCase {
|
||||
XCTAssertFalse(project.hasVariants)
|
||||
}
|
||||
|
||||
func testPreviewProjectMapsNonemptyAIResultURLsInCanonicalOrder() {
|
||||
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",
|
||||
aiRefinedURL: " https://cdn.example.com/refined.jpg ",
|
||||
aiAtmosphereURL: "https://cdn.example.com/atmosphere.jpg"
|
||||
)
|
||||
|
||||
let project = TravelAlbumPreviewProject(material: material)
|
||||
|
||||
XCTAssertEqual(project.orderedAssets.map(\.kind), [.original, .retouched, .atmosphere])
|
||||
XCTAssertEqual(
|
||||
project.asset(for: .retouched)?.displayURL,
|
||||
"https://cdn.example.com/refined.jpg"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
project.asset(for: .atmosphere)?.previewURL,
|
||||
"https://cdn.example.com/atmosphere.jpg"
|
||||
)
|
||||
XCTAssertNil(project.asset(for: .cover))
|
||||
XCTAssertTrue(project.hasVariants)
|
||||
}
|
||||
|
||||
func testPreviewProjectIgnoresBlankAIResultURLs() {
|
||||
let material = TravelAlbumMaterial(
|
||||
id: 8,
|
||||
fileUrl: "https://cdn.example.com/original.jpg",
|
||||
aiRefinedURL: " \n ",
|
||||
aiAtmosphereURL: "\t"
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
TravelAlbumPreviewProject(material: material).orderedAssets.map(\.kind),
|
||||
[.original]
|
||||
)
|
||||
}
|
||||
|
||||
func testPreviewAssetFallsBackToAvailableURLForBothQualityLevels() {
|
||||
let missingOriginal = TravelAlbumPreviewAsset(
|
||||
id: "cover-only",
|
||||
@@ -91,6 +131,36 @@ final class TravelAlbumModelsTests: XCTestCase {
|
||||
XCTAssertEqual(nodes[target], TravelAlbumPreviewNode(projectIndex: 0, kind: .original))
|
||||
}
|
||||
|
||||
func testPreviewDeletionPrefersNextThenFallsBackToPreviousAndClosesWhenEmpty() {
|
||||
XCTAssertEqual(
|
||||
TravelAlbumPreviewNavigator.projectIndexAfterDeletion(
|
||||
deletedProjectIndex: 0,
|
||||
remainingProjectCount: 2
|
||||
),
|
||||
0
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumPreviewNavigator.projectIndexAfterDeletion(
|
||||
deletedProjectIndex: 1,
|
||||
remainingProjectCount: 2
|
||||
),
|
||||
1
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumPreviewNavigator.projectIndexAfterDeletion(
|
||||
deletedProjectIndex: 2,
|
||||
remainingProjectCount: 2
|
||||
),
|
||||
1
|
||||
)
|
||||
XCTAssertNil(
|
||||
TravelAlbumPreviewNavigator.projectIndexAfterDeletion(
|
||||
deletedProjectIndex: 0,
|
||||
remainingProjectCount: 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func testPlaceholderPreviewActionsReturnUnavailableWithoutMutation() async {
|
||||
let handler = PlaceholderTravelAlbumPreviewActionHandler()
|
||||
let aiResult = await handler.requestAIRetouch(originalMaterialId: 9)
|
||||
@@ -153,6 +223,10 @@ final class TravelAlbumModelsTests: XCTestCase {
|
||||
"file_size": 2048,
|
||||
"cover_url": "",
|
||||
"is_purchased": false,
|
||||
"ai_retouch_status": 3,
|
||||
"ai_retouch_status_name": "AI已修",
|
||||
"ai_refined_url": "https://cdn/refined.jpg",
|
||||
"ai_atmosphere_url": "https://cdn/atmosphere.jpg",
|
||||
"created_at": "",
|
||||
"updated_at": ""
|
||||
}
|
||||
@@ -162,6 +236,70 @@ final class TravelAlbumModelsTests: XCTestCase {
|
||||
XCTAssertEqual(material.userEquityTravelId, 7)
|
||||
XCTAssertEqual(material.fileName, "IMG_0001.JPG")
|
||||
XCTAssertFalse(material.isPurchased)
|
||||
XCTAssertEqual(material.aiRetouchStatus, 3)
|
||||
XCTAssertEqual(material.aiRetouchStatusName, "AI已修")
|
||||
XCTAssertEqual(material.aiRefinedURL, "https://cdn/refined.jpg")
|
||||
XCTAssertEqual(material.aiAtmosphereURL, "https://cdn/atmosphere.jpg")
|
||||
}
|
||||
|
||||
func testTravelAlbumMaterialDefaultsInvalidAIFields() throws {
|
||||
let json = """
|
||||
{
|
||||
"id": 11,
|
||||
"user_equity_travel_id": 7,
|
||||
"status": 1,
|
||||
"order_number": "",
|
||||
"user_id": 9,
|
||||
"file_name": "IMG_0001.JPG",
|
||||
"file_type": 2,
|
||||
"file_url": "https://cdn/a.jpg",
|
||||
"file_size": 2048,
|
||||
"cover_url": "",
|
||||
"is_purchased": false,
|
||||
"ai_retouch_status": "invalid",
|
||||
"ai_retouch_status_name": null,
|
||||
"ai_refined_url": 123,
|
||||
"created_at": "",
|
||||
"updated_at": ""
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let material = try JSONDecoder().decode(TravelAlbumMaterial.self, from: json)
|
||||
|
||||
XCTAssertEqual(material.aiRetouchStatus, 0)
|
||||
XCTAssertEqual(material.aiRetouchStatusName, "")
|
||||
XCTAssertEqual(material.aiRefinedURL, "")
|
||||
XCTAssertEqual(material.aiAtmosphereURL, "")
|
||||
}
|
||||
|
||||
func testTravelAlbumMaterialBadgePriorityAndFallbacks() {
|
||||
XCTAssertNil(TravelAlbumMaterial(aiRetouchStatus: 0).badgePresentation)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(isPurchased: true, aiRetouchStatus: 0, aiRetouchStatusName: "已上传")
|
||||
.badgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .purchased, text: "已购")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 1, aiRetouchStatusName: " 排队中 ").badgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .pending, text: "排队中")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 2, aiRetouchStatusName: " ").badgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .processing, text: "修图中")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 3).badgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .retouched, text: "AI已修")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 3, aiRetouchStatusName: "AI封面").badgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .cover, text: "AI封面")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 4).badgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .failed, text: "失败")
|
||||
)
|
||||
XCTAssertNil(TravelAlbumMaterial(aiRetouchStatus: 99, aiRetouchStatusName: "未知").badgePresentation)
|
||||
}
|
||||
|
||||
func testDisplayFormatters() {
|
||||
|
||||
Reference in New Issue
Block a user