EditPictureControlTests.swift (14153B)
1 // 2 // EditPictureControlTests.swift 3 // damus 4 // 5 // Created by Daniel D'Aquino on 2024-12-28. 6 // 7 8 import XCTest 9 import SnapshotTesting 10 @testable import damus 11 import SwiftUI 12 13 final class EditPictureControlTests: XCTestCase { 14 typealias ViewModel = EditPictureControlViewModel<MockImageUploadModel> 15 typealias SelectionState = ViewModel.PictureSelectionState 16 17 let mock_uploader = MockMediaUploader() 18 let mock_url = URL(string: get_test_uploaded_url())! 19 let test_image = UIImage(named: "bitcoin-p2p")! 20 let mock_keypair = test_keypair 21 let mock_pubkey = test_keypair.pubkey 22 23 override func setUp() { 24 super.setUp() 25 } 26 27 @MainActor 28 func testPFPLibrarySelection() async { 29 let expectation = XCTestExpectation(description: "Received URL") 30 let view_model = ViewModel( 31 context: .profile_picture, 32 pubkey: mock_pubkey, 33 current_image_url: .constant(mock_url), 34 state: .ready, 35 keypair: mock_keypair, 36 uploader: mock_uploader, 37 callback: { url in 38 XCTAssertEqual(url, URL(string: get_test_uploaded_url())) 39 expectation.fulfill() 40 } 41 ) 42 43 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 44 45 view_model.select_image_from_library() 46 XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_library) 47 48 view_model.request_upload_authorization(.uiimage(test_image)) 49 XCTAssertEqual(view_model.state.step, SelectionState.Step.confirming_upload) 50 51 view_model.confirm_upload_authorization() 52 XCTAssertEqual(view_model.state.step, SelectionState.Step.cropping) 53 54 view_model.finished_cropping(croppedImage: test_image.resized(to: CGSize(width: 10, height: 10))) 55 XCTAssertEqual(view_model.state.step, SelectionState.Step.uploading) 56 57 // Wait to receive URL 58 await fulfillment(of: [expectation], timeout: 5) 59 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 60 } 61 62 @MainActor 63 func testBannerLibrarySelection() async { 64 let expectation = XCTestExpectation(description: "Received URL") 65 let view_model = ViewModel( 66 context: .normal, 67 pubkey: mock_pubkey, 68 current_image_url: .constant(mock_url), 69 state: .ready, 70 keypair: mock_keypair, 71 uploader: mock_uploader, 72 callback: { url in 73 XCTAssertEqual(url, URL(string: get_test_uploaded_url())) 74 expectation.fulfill() 75 } 76 ) 77 78 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 79 80 view_model.select_image_from_library() 81 XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_library) 82 83 let test_image = UIImage(named: "bitcoin-p2p")! 84 view_model.request_upload_authorization(.uiimage(test_image)) 85 XCTAssertEqual(view_model.state.step, SelectionState.Step.confirming_upload) 86 87 view_model.confirm_upload_authorization() 88 XCTAssertEqual(view_model.state.step, SelectionState.Step.uploading) 89 90 // Wait to receive URL 91 await fulfillment(of: [expectation], timeout: 5) 92 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 93 } 94 95 @MainActor 96 func testPFPCameraSelection() async { 97 let expectation = XCTestExpectation(description: "Received URL") 98 let view_model = ViewModel( 99 context: .profile_picture, 100 pubkey: mock_pubkey, 101 current_image_url: .constant(mock_url), 102 state: .ready, 103 keypair: mock_keypair, 104 uploader: mock_uploader, 105 callback: { url in 106 XCTAssertEqual(url, URL(string: get_test_uploaded_url())) 107 expectation.fulfill() 108 } 109 ) 110 111 // Ready 112 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 113 114 // Take picture 115 view_model.select_image_from_camera() 116 XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_camera) 117 XCTAssertEqual(view_model.state.show_camera, true) 118 119 // Confirm upload 120 view_model.request_upload_authorization(.uiimage(test_image)) 121 XCTAssertEqual(view_model.state.step, SelectionState.Step.confirming_upload) 122 XCTAssertEqual(view_model.state.is_confirming_upload, true) 123 XCTAssertEqual(view_model.state.show_camera, false) 124 125 // Confirm and crop 126 view_model.confirm_upload_authorization() 127 XCTAssertEqual(view_model.state.step, SelectionState.Step.cropping) 128 XCTAssertEqual(view_model.state.show_image_cropper, true) 129 XCTAssertEqual(view_model.state.is_confirming_upload, false) 130 131 // Finish cropping and upload 132 view_model.finished_cropping(croppedImage: test_image.resized(to: CGSize(width: 10, height: 10))) 133 XCTAssertEqual(view_model.state.step, SelectionState.Step.uploading) 134 XCTAssertEqual(view_model.state.show_image_cropper, false) 135 136 // Wait to receive URL 137 await fulfillment(of: [expectation], timeout: 5) 138 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 139 } 140 141 @MainActor 142 func testBannerCameraSelection() async { 143 let expectation = XCTestExpectation(description: "Received URL") 144 let view_model = ViewModel( 145 context: .normal, 146 pubkey: mock_pubkey, 147 current_image_url: .constant(mock_url), 148 state: .ready, 149 keypair: mock_keypair, 150 uploader: mock_uploader, 151 callback: { url in 152 XCTAssertEqual(url, URL(string: get_test_uploaded_url())) 153 expectation.fulfill() 154 } 155 ) 156 157 // Ready 158 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 159 160 // Take picture 161 view_model.select_image_from_camera() 162 XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_camera) 163 XCTAssertEqual(view_model.state.show_camera, true) 164 165 // Confirm upload 166 view_model.request_upload_authorization(.uiimage(test_image)) 167 XCTAssertEqual(view_model.state.step, SelectionState.Step.confirming_upload) 168 XCTAssertEqual(view_model.state.is_confirming_upload, true) 169 XCTAssertEqual(view_model.state.show_camera, false) 170 171 // Confirm and upload 172 view_model.confirm_upload_authorization() 173 XCTAssertEqual(view_model.state.step, SelectionState.Step.uploading) 174 XCTAssertEqual(view_model.state.show_image_cropper, false) 175 XCTAssertEqual(view_model.state.is_confirming_upload, false) 176 177 // Wait to receive URL 178 await fulfillment(of: [expectation], timeout: 5) 179 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 180 } 181 182 @MainActor 183 func testPFPUrlSelection() async { 184 let expectation = XCTestExpectation(description: "Received URL") 185 let view_model = ViewModel( 186 context: .profile_picture, 187 pubkey: mock_pubkey, 188 current_image_url: .constant(mock_url), 189 state: .ready, 190 keypair: mock_keypair, 191 uploader: mock_uploader, 192 callback: { url in 193 if url == self.mock_url { 194 expectation.fulfill() 195 } 196 } 197 ) 198 199 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 200 201 view_model.select_image_from_url() 202 XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_url) 203 204 view_model.choose_url(mock_url) 205 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 206 let current_image_url = view_model.current_image_url 207 XCTAssertEqual(current_image_url, mock_url) 208 209 // Wait to receive URL 210 await fulfillment(of: [expectation], timeout: 5) 211 } 212 213 @MainActor 214 func testPFPSelectionWithCancellation() async { 215 let expectation = XCTestExpectation(description: "Received URL") 216 let view_model = ViewModel( 217 context: .profile_picture, 218 pubkey: mock_pubkey, 219 current_image_url: .constant(mock_url), 220 state: .ready, 221 keypair: mock_keypair, 222 uploader: mock_uploader, 223 callback: { url in 224 XCTAssertEqual(url, URL(string: get_test_uploaded_url())) 225 expectation.fulfill() 226 } 227 ) 228 229 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 230 231 // Open camera 232 view_model.select_image_from_camera() 233 XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_camera) 234 XCTAssertTrue(view_model.show_camera.wrappedValue) 235 236 // Dismiss camera 237 view_model.show_camera.wrappedValue = false 238 XCTAssertFalse(view_model.show_camera.wrappedValue) 239 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 240 241 // Open library 242 view_model.select_image_from_library() 243 XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_library) 244 XCTAssertTrue(view_model.show_library.wrappedValue) 245 246 // Dismiss library 247 view_model.show_library.wrappedValue = false 248 XCTAssertFalse(view_model.show_library.wrappedValue) 249 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 250 251 // Select from URL 252 view_model.select_image_from_url() 253 XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_url) 254 XCTAssertTrue(view_model.show_url_sheet.wrappedValue) 255 256 // Dismiss URL sheet 257 view_model.show_url_sheet.wrappedValue = false 258 XCTAssertFalse(view_model.show_url_sheet.wrappedValue) 259 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 260 261 // Select from library and start cropping 262 view_model.select_image_from_library() 263 view_model.request_upload_authorization(.uiimage(test_image)) 264 view_model.confirm_upload_authorization() 265 XCTAssertEqual(view_model.state.step, SelectionState.Step.cropping) 266 XCTAssertTrue(view_model.show_image_cropper.wrappedValue) 267 268 // Cancel during cropping 269 view_model.show_image_cropper.wrappedValue = false 270 XCTAssertEqual(view_model.state.step, SelectionState.Step.ready) 271 } 272 273 /* 274 @MainActor 275 func testEditPictureControlFirstTimeSetup() async { 276 var current_image_url: URL? = nil 277 278 let view_model = EditPictureControl.Model( 279 context: .profile_picture, 280 pubkey: mock_pubkey, 281 current_image_url: Binding(get: { return current_image_url }, set: { current_image_url = $0 }), 282 state: .ready, 283 keypair: mock_keypair, 284 uploader: mock_uploader, 285 callback: { url in 286 return 287 } 288 ) 289 290 // Setup the test view 291 let test_view = EditPictureControl( 292 model: view_model, 293 style: .init(size: 25, first_time_setup: true), 294 callback: { url in return } 295 ) 296 let hostView = UIHostingController(rootView: test_view) 297 298 sleep(2) // Wait a bit for things to load 299 assertSnapshot(matching: hostView, as: .image(on: .iPhoneSe(.portrait))) 300 } 301 302 @MainActor 303 func testEditPictureControlNotFirstTimeSetup() async { 304 var current_image_url: URL? = nil 305 306 let view_model = EditPictureControl.Model( 307 context: .profile_picture, 308 pubkey: mock_pubkey, 309 current_image_url: Binding(get: { return current_image_url }, set: { current_image_url = $0 }), 310 state: .ready, 311 keypair: mock_keypair, 312 uploader: mock_uploader, 313 callback: { url in 314 return 315 } 316 ) 317 318 // Setup the test view 319 let test_view = EditPictureControl( 320 model: view_model, 321 style: .init(size: nil, first_time_setup: false), 322 callback: { url in return } 323 ) 324 let hostView = UIHostingController(rootView: test_view) 325 326 sleep(2) // Wait a bit for things to load 327 assertSnapshot(matching: hostView, as: .image(on: .iPhoneSe(.portrait))) 328 } 329 */ 330 331 // MARK: Mock classes 332 333 class MockMediaUploader: MediaUploaderProtocol { 334 var nameParam: String { return "name_param" } 335 var mediaTypeParam: String { return "media_type_param" } 336 var supportsVideo: Bool { return true } 337 var requiresNip98: Bool { return true } 338 var postAPI: String { return "http://localhost:8000" } 339 340 func getMediaURL(from data: Data) -> String? { 341 return "http://localhost:8000" 342 } 343 344 func mediaTypeValue(for mediaType: damus.ImageUploadMediaType) -> String? { 345 return "media_type_value" 346 } 347 348 var uploadCalled = false 349 var uploadCompletion: (() -> Void)? 350 } 351 352 class MockImageUploadModel: ImageUploadModelProtocol { 353 required init() {} 354 355 func start(media: damus.MediaUpload, uploader: any damus.MediaUploaderProtocol, mediaType: damus.ImageUploadMediaType, keypair: damus.Keypair?) async -> damus.ImageUploadResult { 356 return damus.ImageUploadResult.success(get_test_uploaded_url()) 357 } 358 } 359 } 360 361 fileprivate func get_test_uploaded_url() -> String { 362 return "https://example.com/newimage.jpg" 363 } 364 365 fileprivate extension UIImage { 366 static func from(url: URL) throws -> UIImage? { 367 let data = try Data(contentsOf: url) 368 return UIImage(data: data) 369 } 370 }