Sync OTG transfer photo list UI
This commit is contained in:
@ -142,6 +142,68 @@ final class WiredCameraTransferViewModelTests: XCTestCase {
|
||||
XCTAssertTrue(viewModel.filteredPhotos().isEmpty)
|
||||
}
|
||||
|
||||
func testTransferPhotoSectionsAndSidebarUseDescendingHalfHourSlots() {
|
||||
let photos = [
|
||||
makeOTGPhotoItem(id: "a", capturedAt: "2026-07-08 12:05:00"),
|
||||
makeOTGPhotoItem(id: "b", capturedAt: "2026-07-08 12:29:59"),
|
||||
makeOTGPhotoItem(id: "c", capturedAt: "2026-07-08 12:30:00"),
|
||||
makeOTGPhotoItem(id: "d", capturedAt: "2026-07-07 18:10:00"),
|
||||
]
|
||||
|
||||
let sections = photos.buildPhotoSections()
|
||||
let groups = photos.buildSidebarGroups()
|
||||
|
||||
XCTAssertEqual(sections.map(\.slotId), [
|
||||
"2026-07-08T12:30",
|
||||
"2026-07-08T12:00",
|
||||
"2026-07-07T18:00",
|
||||
])
|
||||
XCTAssertEqual(sections[0].headerTitle, "07月08日 12:30 - 13:00")
|
||||
XCTAssertEqual(sections[1].photos.map(\.id), ["b", "a"])
|
||||
XCTAssertEqual(groups.map(\.dateLabel), ["07月08日", "07月07日"])
|
||||
XCTAssertEqual(groups.first?.slots.map(\.slotStartLabel), ["12:30", "12:00"])
|
||||
XCTAssertEqual(groups.first?.slots.last?.photoCount, 2)
|
||||
}
|
||||
|
||||
func testSelectedTimeSlotFallsBackWhenTabChanges() throws {
|
||||
let context = makeOTGTestContext()
|
||||
context.store.save([
|
||||
try makePersistedOTGRecord(context: context, id: "pending", capturedAt: "2026-07-08 12:05:00", status: .pending),
|
||||
try makePersistedOTGRecord(context: context, id: "failed", capturedAt: "2026-07-08 12:30:00", status: .failed),
|
||||
], albumId: 9)
|
||||
let viewModel = makeWiredViewModel(
|
||||
context: context,
|
||||
manager: MockWiredCameraConnectionManager(driver: MockCameraDriver(objects: []))
|
||||
)
|
||||
|
||||
viewModel.reloadLocalPhotos()
|
||||
viewModel.selectTimeSlot(id: "2026-07-08T12:00")
|
||||
viewModel.selectTab(.failed)
|
||||
|
||||
XCTAssertEqual(viewModel.selectedTimeSlotId, "2026-07-08T12:30")
|
||||
}
|
||||
|
||||
func testSelectionModeAllowsPendingAndFailedOnly() throws {
|
||||
let context = makeOTGTestContext()
|
||||
context.store.save([
|
||||
try makePersistedOTGRecord(context: context, id: "pending", capturedAt: "2026-07-08 12:05:00", status: .pending),
|
||||
try makePersistedOTGRecord(context: context, id: "failed", capturedAt: "2026-07-08 12:10:00", status: .failed),
|
||||
try makePersistedOTGRecord(context: context, id: "uploaded", capturedAt: "2026-07-08 12:15:00", status: .uploaded),
|
||||
], albumId: 9)
|
||||
let viewModel = makeWiredViewModel(
|
||||
context: context,
|
||||
manager: MockWiredCameraConnectionManager(driver: MockCameraDriver(objects: []))
|
||||
)
|
||||
|
||||
viewModel.reloadLocalPhotos()
|
||||
viewModel.onBatchUploadButtonClick()
|
||||
viewModel.toggleTransferPhotoSelection(photoId: "pending")
|
||||
viewModel.toggleTransferPhotoSelection(photoId: "failed")
|
||||
viewModel.toggleTransferPhotoSelection(photoId: "uploaded")
|
||||
|
||||
XCTAssertEqual(viewModel.selectedPhotoIds, ["pending", "failed"])
|
||||
}
|
||||
|
||||
func testEnteringPageDoesNotListCameraObjects() async {
|
||||
let context = makeOTGTestContext()
|
||||
let object = CameraObject(id: "A|10", filename: "A.JPG", fileSize: 10, capturedAt: Date())
|
||||
@ -422,6 +484,66 @@ private func makeWiredViewModel(
|
||||
)
|
||||
}
|
||||
|
||||
private func makeOTGPhotoItem(
|
||||
id: String,
|
||||
capturedAt: String,
|
||||
status: TravelAlbumOTGUploadStatus = .pending
|
||||
) -> TravelAlbumOTGPhotoItem {
|
||||
TravelAlbumOTGPhotoItem(
|
||||
id: id,
|
||||
sourceId: id,
|
||||
fileName: "\(id).JPG",
|
||||
thumbnailURL: nil,
|
||||
capturedAt: capturedAt,
|
||||
fileSizeText: "1 KB",
|
||||
fileSizeBytes: 1024,
|
||||
status: status,
|
||||
progress: 0,
|
||||
errorMessage: nil,
|
||||
localPath: "originals/\(id).JPG",
|
||||
remoteUrl: ""
|
||||
)
|
||||
}
|
||||
|
||||
private func makeOTGRecord(
|
||||
id: String,
|
||||
capturedAt: String,
|
||||
status: TravelAlbumOTGUploadStatus
|
||||
) -> TravelAlbumOTGPhotoRecord {
|
||||
TravelAlbumOTGPhotoRecord(
|
||||
id: id,
|
||||
fileName: "\(id).JPG",
|
||||
localPath: "originals/\(id).JPG",
|
||||
capturedAt: capturedAt,
|
||||
fileSizeBytes: 1024,
|
||||
status: status,
|
||||
albumId: 9,
|
||||
userId: "u1"
|
||||
)
|
||||
}
|
||||
|
||||
private func makePersistedOTGRecord(
|
||||
context: OTGTestContext,
|
||||
id: String,
|
||||
capturedAt: String,
|
||||
status: TravelAlbumOTGUploadStatus
|
||||
) throws -> TravelAlbumOTGPhotoRecord {
|
||||
let relativePath = "originals/\(id).JPG"
|
||||
let directory = try context.store.originalsDirectory(albumId: 9)
|
||||
let url = directory.appendingPathComponent("\(id).JPG")
|
||||
try Data([0xFF, 0xD8, 0xFF]).write(to: url)
|
||||
return TravelAlbumOTGPhotoRecord(
|
||||
id: id,
|
||||
fileName: "\(id).JPG",
|
||||
localPath: relativePath,
|
||||
capturedAt: capturedAt,
|
||||
fileSizeBytes: 3,
|
||||
status: status,
|
||||
albumId: 9,
|
||||
userId: "u1"
|
||||
)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func makeOTGTestContext() -> OTGTestContext {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
|
||||
Reference in New Issue
Block a user