feat: redesign travel album management
This commit is contained in:
@@ -80,6 +80,7 @@ final class TravelAlbumDetailViewModelTests: XCTestCase {
|
||||
api.infoResponse = TravelAlbum(id: 2, name: "详情")
|
||||
api.materialListResponses = [
|
||||
TravelAlbumListResponse(total: 2, list: []),
|
||||
TravelAlbumListResponse(total: 1, list: []),
|
||||
TravelAlbumListResponse(total: 2, list: [TravelAlbumMaterial(id: 1), TravelAlbumMaterial(id: 2)]),
|
||||
]
|
||||
let viewModel = TravelAlbumDetailViewModel(albumId: 2)
|
||||
@@ -88,7 +89,28 @@ final class TravelAlbumDetailViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(viewModel.album?.name, "详情")
|
||||
XCTAssertEqual(viewModel.allPhotoCount, 2)
|
||||
XCTAssertEqual(viewModel.purchasedPhotoCount, 1)
|
||||
XCTAssertEqual(viewModel.materials.count, 2)
|
||||
XCTAssertEqual(api.materialRequests.map(\.pageSize), [1, 1, 30])
|
||||
XCTAssertEqual(api.materialRequests.map(\.isPurchased), [nil, 1, nil])
|
||||
}
|
||||
|
||||
func testCountFailuresDoNotBlockMaterialList() async {
|
||||
let api = TravelAlbumMockAPI()
|
||||
api.infoResponse = TravelAlbum(id: 2, name: "详情")
|
||||
api.materialListFailingCallIndexes = [0, 1]
|
||||
api.materialListResponses = [
|
||||
TravelAlbumListResponse(total: 3, list: [TravelAlbumMaterial(id: 7)]),
|
||||
]
|
||||
let viewModel = TravelAlbumDetailViewModel(albumId: 2)
|
||||
|
||||
await viewModel.refreshAll(api: api)
|
||||
|
||||
XCTAssertEqual(api.materialRequests.count, 3)
|
||||
XCTAssertEqual(viewModel.materials.map(\.id), [7])
|
||||
XCTAssertEqual(viewModel.allPhotoCount, 3)
|
||||
XCTAssertEqual(viewModel.purchasedPhotoCount, 0)
|
||||
XCTAssertFalse(viewModel.isLoading)
|
||||
}
|
||||
|
||||
func testTabAndSortTriggerMaterialRequests() async {
|
||||
@@ -106,6 +128,41 @@ final class TravelAlbumDetailViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(api.materialRequests.last?.orderBy, 4)
|
||||
}
|
||||
|
||||
func testSortOptionsProvideCompactCurrentStateTitles() {
|
||||
XCTAssertEqual(TravelAlbumDetailViewModel.SortOption.createdAsc.compactTitle, "时间 ↑")
|
||||
XCTAssertEqual(TravelAlbumDetailViewModel.SortOption.createdDesc.compactTitle, "时间 ↓")
|
||||
XCTAssertEqual(TravelAlbumDetailViewModel.SortOption.fileNameAsc.compactTitle, "名称 ↑")
|
||||
XCTAssertEqual(TravelAlbumDetailViewModel.SortOption.fileNameDesc.compactTitle, "名称 ↓")
|
||||
}
|
||||
|
||||
func testAlbumCoverFallsBackToFirstMaterial() {
|
||||
let album = TravelAlbum(id: 2, coverUrl: "")
|
||||
let coverMaterial = TravelAlbumMaterial(id: 1, fileUrl: "original", coverUrl: "https://cdn.example.com/cover.jpg")
|
||||
let originalMaterial = TravelAlbumMaterial(id: 2, fileUrl: "https://cdn.example.com/original.jpg", coverUrl: "")
|
||||
|
||||
XCTAssertEqual(
|
||||
TravelAlbumDisplayFormatter.albumCoverURL(album: album, materials: [coverMaterial]),
|
||||
"https://cdn.example.com/cover.jpg"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumDisplayFormatter.albumCoverURL(album: album, materials: [originalMaterial]),
|
||||
"https://cdn.example.com/original.jpg"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
TravelAlbumDisplayFormatter.albumCoverURL(
|
||||
album: TravelAlbum(id: 2, coverUrl: "https://cdn.example.com/album.jpg"),
|
||||
materials: [coverMaterial]
|
||||
),
|
||||
"https://cdn.example.com/album.jpg"
|
||||
)
|
||||
}
|
||||
|
||||
func testCreationTimeFormatting() {
|
||||
XCTAssertEqual(TravelAlbumDisplayFormatter.creationTimeText("2026-08-03 14:05:22"), "2026/08/03 14:05")
|
||||
XCTAssertEqual(TravelAlbumDisplayFormatter.creationTimeText("2026-08-03T14:05:22+08:00"), "2026/08/03 14:05")
|
||||
XCTAssertEqual(TravelAlbumDisplayFormatter.creationTimeText(""), "--")
|
||||
}
|
||||
|
||||
func testOnlyUnpurchasedMaterialCanBeSelected() {
|
||||
let viewModel = TravelAlbumDetailViewModel(albumId: 2)
|
||||
viewModel.toggleSelectionMode()
|
||||
@@ -954,6 +1011,7 @@ final class TravelAlbumMockAPI: TravelAlbumServing {
|
||||
var createResponse = TravelAlbumCreateResponse(id: 0)
|
||||
var infoResponse = TravelAlbum()
|
||||
var materialListResponses: [TravelAlbumListResponse<TravelAlbumMaterial>] = []
|
||||
var materialListFailingCallIndexes: Set<Int> = []
|
||||
var uploadMaterialResponse = TravelAlbumMaterial()
|
||||
var materialClientPhotoIdsResponse = TravelAlbumMaterialClientPhotoIDsResponse(clientPhotoIds: [])
|
||||
var mpCodeResponse = TravelAlbumMpCodeResponse(mpCodeOssUrl: "")
|
||||
@@ -993,6 +1051,7 @@ final class TravelAlbumMockAPI: TravelAlbumServing {
|
||||
orderBy: Int,
|
||||
isPurchased: Int?
|
||||
) async throws -> TravelAlbumListResponse<TravelAlbumMaterial> {
|
||||
let callIndex = materialRequests.count
|
||||
materialRequests.append(
|
||||
MaterialRequest(
|
||||
userEquityTravelId: userEquityTravelId,
|
||||
@@ -1002,6 +1061,9 @@ final class TravelAlbumMockAPI: TravelAlbumServing {
|
||||
isPurchased: isPurchased
|
||||
)
|
||||
)
|
||||
if materialListFailingCallIndexes.contains(callIndex) {
|
||||
throw APIError.serverCode(500, "素材数量加载失败")
|
||||
}
|
||||
if materialListResponses.isEmpty { return TravelAlbumListResponse() }
|
||||
return materialListResponses.removeFirst()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user