fix: 优化相册素材状态与删除保护
This commit is contained in:
@@ -1149,9 +1149,8 @@ final class TravelAlbumDetailViewControllerTests: XCTestCase {
|
||||
XCTAssertFalse(controller.view.allLabels().contains { $0.text == "刷新失败" })
|
||||
}
|
||||
|
||||
func testMaterialCellShowsSemanticStatusBadgeWithoutOverlappingSelectionCheck() throws {
|
||||
func testMaterialCellShowsIndependentPurchaseAndAIRetouchBadgesWithoutOverlappingSelectionCheck() throws {
|
||||
let cases: [(TravelAlbumMaterial, String, UInt)] = [
|
||||
(TravelAlbumMaterial(isPurchased: true), "已购", 0x475569),
|
||||
(TravelAlbumMaterial(aiRetouchStatus: 1), "待处理", 0xB45309),
|
||||
(TravelAlbumMaterial(aiRetouchStatus: 2), "修图中", 0x1D4ED8),
|
||||
(TravelAlbumMaterial(aiRetouchStatus: 3), "AI已修", 0x047857),
|
||||
@@ -1170,12 +1169,12 @@ final class TravelAlbumDetailViewControllerTests: XCTestCase {
|
||||
|
||||
let badgeView = try XCTUnwrap(
|
||||
cell.findSubview {
|
||||
$0.accessibilityIdentifier == "travelAlbum.materialStatusBadge"
|
||||
$0.accessibilityIdentifier == "travelAlbum.materialAIRetouchBadge"
|
||||
}
|
||||
)
|
||||
let badgeLabel = try XCTUnwrap(
|
||||
cell.findSubview {
|
||||
$0.accessibilityIdentifier == "travelAlbum.materialStatusBadgeLabel"
|
||||
$0.accessibilityIdentifier == "travelAlbum.materialAIRetouchBadgeLabel"
|
||||
} as? UILabel
|
||||
)
|
||||
let selectionCheck = try XCTUnwrap(
|
||||
@@ -1188,22 +1187,61 @@ final class TravelAlbumDetailViewControllerTests: XCTestCase {
|
||||
XCTAssertEqual(badgeLabel.text, expectedText)
|
||||
XCTAssertEqual(badgeView.backgroundColor?.travelAlbumTestHexRGB, expectedColor)
|
||||
XCTAssertLessThan(badgeView.frame.maxX, selectionCheck.frame.minX)
|
||||
XCTAssertTrue(cell.accessibilityLabel?.contains("状态:\(expectedText)") == true)
|
||||
XCTAssertTrue(cell.accessibilityLabel?.contains("修图状态:\(expectedText)") == true)
|
||||
}
|
||||
|
||||
let combinedCell = TravelAlbumMaterialCell(frame: CGRect(x: 0, y: 0, width: 176, height: 220))
|
||||
combinedCell.apply(
|
||||
material: TravelAlbumMaterial(isPurchased: true, aiRetouchStatus: 2),
|
||||
selectionMode: true,
|
||||
selected: false
|
||||
)
|
||||
combinedCell.layoutIfNeeded()
|
||||
let purchaseBadge = try XCTUnwrap(
|
||||
combinedCell.findSubview {
|
||||
$0.accessibilityIdentifier == "travelAlbum.materialPurchaseBadge"
|
||||
}
|
||||
)
|
||||
let purchaseLabel = try XCTUnwrap(
|
||||
combinedCell.findSubview {
|
||||
$0.accessibilityIdentifier == "travelAlbum.materialPurchaseBadgeLabel"
|
||||
} as? UILabel
|
||||
)
|
||||
let aiRetouchBadge = try XCTUnwrap(
|
||||
combinedCell.findSubview {
|
||||
$0.accessibilityIdentifier == "travelAlbum.materialAIRetouchBadge"
|
||||
}
|
||||
)
|
||||
XCTAssertFalse(purchaseBadge.isHidden)
|
||||
XCTAssertFalse(aiRetouchBadge.isHidden)
|
||||
XCTAssertEqual(purchaseLabel.text, "已购")
|
||||
XCTAssertEqual(purchaseBadge.backgroundColor?.travelAlbumTestHexRGB, 0x475569)
|
||||
XCTAssertGreaterThan(purchaseBadge.frame.minY, aiRetouchBadge.frame.maxY)
|
||||
XCTAssertEqual(purchaseBadge.frame.minX, 6, accuracy: 0.5)
|
||||
XCTAssertEqual(purchaseBadge.frame.maxY, 170, accuracy: 0.5)
|
||||
XCTAssertTrue(combinedCell.accessibilityLabel?.contains("购买状态:已购") == true)
|
||||
XCTAssertTrue(combinedCell.accessibilityLabel?.contains("修图状态:修图中") == true)
|
||||
|
||||
let hiddenCell = TravelAlbumMaterialCell(frame: CGRect(x: 0, y: 0, width: 176, height: 220))
|
||||
hiddenCell.apply(
|
||||
material: TravelAlbumMaterial(aiRetouchStatus: 0, aiRetouchStatusName: "已上传"),
|
||||
selectionMode: false,
|
||||
selected: false
|
||||
)
|
||||
let hiddenBadge = try XCTUnwrap(
|
||||
let hiddenPurchaseBadge = try XCTUnwrap(
|
||||
hiddenCell.findSubview {
|
||||
$0.accessibilityIdentifier == "travelAlbum.materialStatusBadge"
|
||||
$0.accessibilityIdentifier == "travelAlbum.materialPurchaseBadge"
|
||||
}
|
||||
)
|
||||
XCTAssertTrue(hiddenBadge.isHidden)
|
||||
XCTAssertFalse(hiddenCell.accessibilityLabel?.contains("状态:") == true)
|
||||
let hiddenAIRetouchBadge = try XCTUnwrap(
|
||||
hiddenCell.findSubview {
|
||||
$0.accessibilityIdentifier == "travelAlbum.materialAIRetouchBadge"
|
||||
}
|
||||
)
|
||||
XCTAssertTrue(hiddenPurchaseBadge.isHidden)
|
||||
XCTAssertTrue(hiddenAIRetouchBadge.isHidden)
|
||||
XCTAssertFalse(hiddenCell.accessibilityLabel?.contains("购买状态:") == true)
|
||||
XCTAssertFalse(hiddenCell.accessibilityLabel?.contains("修图状态:") == true)
|
||||
}
|
||||
|
||||
private func waitUntil(_ condition: @escaping () -> Bool) async {
|
||||
|
||||
@@ -399,34 +399,48 @@ final class TravelAlbumModelsTests: XCTestCase {
|
||||
XCTAssertEqual(material.aiAtmosphereURL, "")
|
||||
}
|
||||
|
||||
func testTravelAlbumMaterialBadgePriorityAndFallbacks() {
|
||||
XCTAssertNil(TravelAlbumMaterial(aiRetouchStatus: 0).badgePresentation)
|
||||
func testTravelAlbumMaterialPurchaseAndAIRetouchBadgesAreIndependent() {
|
||||
XCTAssertNil(TravelAlbumMaterial().purchaseBadgePresentation)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(isPurchased: true, aiRetouchStatus: 0, aiRetouchStatusName: "已上传")
|
||||
.badgePresentation,
|
||||
TravelAlbumMaterial(isPurchased: true).purchaseBadgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .purchased, text: "已购")
|
||||
)
|
||||
XCTAssertNil(TravelAlbumMaterial(isPurchased: true).aiRetouchBadgePresentation)
|
||||
let purchasedAndProcessing = TravelAlbumMaterial(isPurchased: true, aiRetouchStatus: 2)
|
||||
XCTAssertEqual(
|
||||
purchasedAndProcessing.purchaseBadgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .purchased, text: "已购")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 1, aiRetouchStatusName: " 排队中 ").badgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .pending, text: "排队中")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 2, aiRetouchStatusName: " ").badgePresentation,
|
||||
purchasedAndProcessing.aiRetouchBadgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .processing, text: "修图中")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 3).badgePresentation,
|
||||
TravelAlbumMaterial(aiRetouchStatus: 1, aiRetouchStatusName: " 排队中 ")
|
||||
.aiRetouchBadgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .pending, text: "排队中")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 2, aiRetouchStatusName: " ").aiRetouchBadgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .processing, text: "修图中")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 3).aiRetouchBadgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .retouched, text: "AI已修")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 3, aiRetouchStatusName: "AI封面").badgePresentation,
|
||||
TravelAlbumMaterial(aiRetouchStatus: 3, aiRetouchStatusName: "AI封面")
|
||||
.aiRetouchBadgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .cover, text: "AI封面")
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 4).badgePresentation,
|
||||
TravelAlbumMaterial(aiRetouchStatus: 4).aiRetouchBadgePresentation,
|
||||
TravelAlbumMaterialBadgePresentation(kind: .failed, text: "失败")
|
||||
)
|
||||
XCTAssertNil(TravelAlbumMaterial(aiRetouchStatus: 99, aiRetouchStatusName: "未知").badgePresentation)
|
||||
XCTAssertNil(
|
||||
TravelAlbumMaterial(aiRetouchStatus: 99, aiRetouchStatusName: "未知")
|
||||
.aiRetouchBadgePresentation
|
||||
)
|
||||
}
|
||||
|
||||
func testDisplayFormatters() {
|
||||
|
||||
@@ -279,14 +279,22 @@ final class TravelAlbumDetailViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(TravelAlbumDisplayFormatter.creationTimeText(""), "--")
|
||||
}
|
||||
|
||||
func testOnlyUnpurchasedMaterialCanBeSelected() {
|
||||
func testAllMaterialsCanBeSelectedAndPurchasedTabSupportsSelectionMode() async {
|
||||
let api = TravelAlbumMockAPI()
|
||||
api.materialListResponses = [TravelAlbumListResponse(total: 1, list: [])]
|
||||
let viewModel = TravelAlbumDetailViewModel(albumId: 2)
|
||||
await viewModel.selectTab(.purchased, api: api)
|
||||
viewModel.toggleSelectionMode()
|
||||
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 1, status: 2))
|
||||
XCTAssertTrue(viewModel.selectedMaterialIds.isEmpty)
|
||||
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 1, isPurchased: true))
|
||||
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 2, isPurchased: false))
|
||||
|
||||
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 1, status: 1))
|
||||
XCTAssertEqual(viewModel.selectedMaterialIds, [1])
|
||||
XCTAssertTrue(viewModel.isSelectionMode)
|
||||
XCTAssertEqual(viewModel.selectedMaterialIds, [1, 2])
|
||||
XCTAssertEqual(viewModel.selectedPurchasedMaterialCount, 1)
|
||||
XCTAssertEqual(
|
||||
viewModel.deleteSelectedConfirmationMessage,
|
||||
"已选择 2 张素材,其中 1 张已购买。不会删除已购买的项目,只会删除 1 张未购买的项目。"
|
||||
)
|
||||
}
|
||||
|
||||
func testCompletingAIRetouchClearsSelectionModeAndSelectedMaterials() {
|
||||
@@ -405,6 +413,42 @@ final class TravelAlbumDetailViewModelTests: XCTestCase {
|
||||
XCTAssertFalse(viewModel.isSelectionMode)
|
||||
XCTAssertTrue(viewModel.selectedMaterialIds.isEmpty)
|
||||
}
|
||||
|
||||
func testDeleteSelectedMaterialsFiltersPurchasedItemsFromBatchRequest() async {
|
||||
let api = TravelAlbumMockAPI()
|
||||
api.materialListResponses = [
|
||||
TravelAlbumListResponse(total: 0, list: []),
|
||||
TravelAlbumListResponse(total: 0, list: []),
|
||||
TravelAlbumListResponse(total: 0, list: []),
|
||||
]
|
||||
let viewModel = TravelAlbumDetailViewModel(albumId: 5)
|
||||
viewModel.toggleSelectionMode()
|
||||
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 10, isPurchased: true))
|
||||
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 11, isPurchased: false))
|
||||
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 12, isPurchased: true))
|
||||
|
||||
await viewModel.deleteSelectedMaterials(api: api)
|
||||
|
||||
XCTAssertEqual(api.deletedMaterialIDBatches, [[11]])
|
||||
XCTAssertFalse(viewModel.isSelectionMode)
|
||||
XCTAssertTrue(viewModel.selectedMaterialIds.isEmpty)
|
||||
}
|
||||
|
||||
func testDeleteSelectedMaterialsDoesNotRequestAPIWhenAllSelectedItemsArePurchased() async {
|
||||
let api = TravelAlbumMockAPI()
|
||||
let viewModel = TravelAlbumDetailViewModel(albumId: 5)
|
||||
var message = ""
|
||||
viewModel.onShowMessage = { message = $0 }
|
||||
viewModel.toggleSelectionMode()
|
||||
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 10, isPurchased: true))
|
||||
|
||||
await viewModel.deleteSelectedMaterials(api: api)
|
||||
|
||||
XCTAssertTrue(api.deletedMaterialIDBatches.isEmpty)
|
||||
XCTAssertEqual(message, "选中的素材均已购买,未删除任何项目")
|
||||
XCTAssertTrue(viewModel.isSelectionMode)
|
||||
XCTAssertEqual(viewModel.selectedMaterialIds, [10])
|
||||
}
|
||||
}
|
||||
|
||||
/// 有线传输 ViewModel 测试。
|
||||
|
||||
Reference in New Issue
Block a user