diff --git a/Tests/PhotochkTests/SelectionTests.swift b/Tests/PhotochkTests/SelectionTests.swift new file mode 100644 index 0000000..b4a4fe2 --- /dev/null +++ b/Tests/PhotochkTests/SelectionTests.swift @@ -0,0 +1,102 @@ +import Foundation +import Testing + +@testable import Photochk + +@Suite("選択ルール") +@MainActor +struct SelectionTests { + private func asset( + _ name: String, width: Int, height: Int, size: Int64, days: Double + ) -> PhotoAsset { + PhotoAsset( + url: URL(fileURLWithPath: "/tmp/photochk/\(name)"), + fileSize: size, + modificationDate: Date(timeIntervalSince1970: days * 86_400), + captureDate: Date(timeIntervalSince1970: days * 86_400), + pixelWidth: width, + pixelHeight: height + ) + } + + private func makeGroup() -> SimilarityGroup { + let big = asset("big.jpg", width: 4000, height: 3000, size: 5_000_000, days: 10) + let mid = asset("mid.jpg", width: 2000, height: 1500, size: 2_000_000, days: 20) + let small = asset("small.jpg", width: 1000, height: 750, size: 500_000, days: 1) + + return SimilarityGroup( + members: [ + .init(asset: big, relation: .exact), + .init(asset: mid, relation: .similar(distance: 0.1)), + .init(asset: small, relation: .similar(distance: 0.2)), + ], + representativeID: big.url + ) + } + + private func makeModel() -> ScanViewModel { + let model = ScanViewModel() + model.setGroupsForTesting([makeGroup()]) + return model + } + + @Test("最後の1枚は選択できない") + func keepsAtLeastOne() { + let model = makeModel() + let group = model.groups[0] + let ids = group.members.map(\.id) + + model.toggleMark(ids[0], in: group) + model.toggleMark(ids[1], in: group) + #expect(model.markedCount == 2) + + // 3枚目を選ぶと全滅するので拒否される + #expect(model.canMark(ids[2], in: group) == false) + model.toggleMark(ids[2], in: group) + #expect(model.markedCount == 2) + + // すでに選んでいるものは解除できる + #expect(model.canMark(ids[0], in: group) == true) + } + + @Test("解像度が最も高い1枚を残す") + func keepHighestResolution() { + let model = makeModel() + model.applyKeepRule(.highestResolution) + + let kept = model.groups[0].members.filter { !model.markedForDeletion.contains($0.id) } + #expect(kept.count == 1) + #expect(kept[0].asset.fileName == "big.jpg") + } + + @Test("最も新しい1枚 / 最も古い1枚を残す") + func keepNewestAndOldest() { + let model = makeModel() + + model.applyKeepRule(.newest) + var kept = model.groups[0].members.filter { !model.markedForDeletion.contains($0.id) } + #expect(kept.map(\.asset.fileName) == ["mid.jpg"]) + + model.applyKeepRule(.oldest) + kept = model.groups[0].members.filter { !model.markedForDeletion.contains($0.id) } + #expect(kept.map(\.asset.fileName) == ["small.jpg"]) + } + + @Test("選択解除ですべての印が消える") + func clearMarks() { + let model = makeModel() + model.applyKeepRule(.highestResolution) + #expect(model.markedCount == 2) + + model.clearMarks() + #expect(model.markedCount == 0) + #expect(model.markedBytes == 0) + } + + @Test("選択した容量の合計が集計される") + func markedBytes() { + let model = makeModel() + model.applyKeepRule(.highestResolution) + #expect(model.markedBytes == 2_500_000) + } +}