diff --git a/Modules/Commons/Extensions/NSLayoutConstraint+Extensions.swift b/Modules/Commons/Extensions/NSLayoutConstraint+Extensions.swift index c97bf64..2b11a74 100644 --- a/Modules/Commons/Extensions/NSLayoutConstraint+Extensions.swift +++ b/Modules/Commons/Extensions/NSLayoutConstraint+Extensions.swift @@ -271,16 +271,16 @@ public extension NSLayoutConstraint { static func centerX( firstView: UIView, secondView: UIView, - constant: Float = 0) - -> NSLayoutConstraint { - return NSLayoutConstraint( - item: firstView, - attribute: NSLayoutConstraint.Attribute.centerX, - relatedBy: NSLayoutConstraint.Relation.equal, - toItem: secondView, - attribute: NSLayoutConstraint.Attribute.centerX, - multiplier: 1, - constant: CGFloat(constant) - ) + constant: Float = 0 + ) -> NSLayoutConstraint { + return NSLayoutConstraint( + item: firstView, + attribute: NSLayoutConstraint.Attribute.centerX, + relatedBy: NSLayoutConstraint.Relation.equal, + toItem: secondView, + attribute: NSLayoutConstraint.Attribute.centerX, + multiplier: 1, + constant: CGFloat(constant) + ) } } diff --git a/Modules/Commons/Protocols/Alertable.swift b/Modules/Commons/Protocols/Alertable.swift index f87be6b..61c590a 100644 --- a/Modules/Commons/Protocols/Alertable.swift +++ b/Modules/Commons/Protocols/Alertable.swift @@ -99,7 +99,7 @@ extension Alertable { func alertWithError(_ error: NetworkError) -> UIAlertController { let title = R.string.localizable.errorAlertDefaultTitle() - let message = error.messageToPresentToUser() + let message = error.localizedDescription let actionButton = R.string.localizable.errorAlertDefaultAction() return alertWithActions( @@ -120,7 +120,7 @@ extension Alertable { ] let alert = alertWithActions( title: R.string.localizable.somethingWrong(), - message: error.messageToPresentToUser(), + message: error.localizedDescription, actions: actions, handlers: [ retry, diff --git a/Modules/Commons/Protocols/ViewCodable.swift b/Modules/Commons/Protocols/ViewCoding.swift similarity index 72% rename from Modules/Commons/Protocols/ViewCodable.swift rename to Modules/Commons/Protocols/ViewCoding.swift index d513a9c..b2a0bcc 100644 --- a/Modules/Commons/Protocols/ViewCodable.swift +++ b/Modules/Commons/Protocols/ViewCoding.swift @@ -1,16 +1,18 @@ import UIKit -public protocol ViewCodable { +public protocol ViewCoding { func buildViewHierarchy() func setupConstraints() func additionalSetup() func buildView() } -extension ViewCodable { +extension ViewCoding { public func buildView() { buildViewHierarchy() setupConstraints() additionalSetup() } + + public func additionalSetup() {} } diff --git a/Modules/ImageLoader/Protocols/ImageLoaderServiceProtocol.swift b/Modules/ImageLoader/Protocols/ImageLoaderServiceProtocol.swift index 0b98504..8ceddd0 100644 --- a/Modules/ImageLoader/Protocols/ImageLoaderServiceProtocol.swift +++ b/Modules/ImageLoader/Protocols/ImageLoaderServiceProtocol.swift @@ -3,7 +3,8 @@ import UIKit public typealias ResulImageHandler = (Result) -> Void public protocol ImageLoaderServiceProtocol { - func loadImage(with url: URL, result: @escaping ResulImageHandler) + func downloadImage(with url: URL, result: @escaping ResulImageHandler) + func cancelDownload() func starPrefetchDataSourceOperation(with urls: [URL]) func cancelPrefethcDataSoucerOperation(with urls: [URL]) } diff --git a/Modules/ImageLoader/Providers/NukeImageLoaderProvider.swift b/Modules/ImageLoader/Providers/NukeImageLoaderProvider.swift index 266a617..c9819a7 100644 --- a/Modules/ImageLoader/Providers/NukeImageLoaderProvider.swift +++ b/Modules/ImageLoader/Providers/NukeImageLoaderProvider.swift @@ -3,15 +3,17 @@ import Nuke public final class NukeImageLoaderProvider: ImageLoaderServiceProtocol { private let preheater = ImagePreheater() + private let pipeLine = ImagePipeline.shared + private var task: ImageTask! public init () { configure() } - public func loadImage(with url: URL, result: @escaping ResulImageHandler) { + public func downloadImage(with url: URL, result: @escaping ResulImageHandler) { let request = ImageRequest(url: url) - ImagePipeline.shared.loadImage(with: request, queue: nil, progress: nil) { response in + task = pipeLine.loadImage(with: request, queue: nil, progress: nil) { response in DispatchQueue.main.async { switch response { case let .success(imageResponse): @@ -23,6 +25,10 @@ public final class NukeImageLoaderProvider: ImageLoaderServiceProtocol { } } + public func cancelDownload() { + task.cancel() + } + private func configure() { let contentModes = ImageLoadingOptions.ContentModes( success: .scaleAspectFill, diff --git a/Modules/Network/Enums/NetworkError.swift b/Modules/Network/Enums/NetworkError.swift index 1a9e27f..f3fb5d0 100644 --- a/Modules/Network/Enums/NetworkError.swift +++ b/Modules/Network/Enums/NetworkError.swift @@ -11,13 +11,7 @@ public enum NetworkError: Error { case badRequest case outDated - public init(description: String) { - self = .custom(description: description) - } -} - -extension NetworkError { - func messageToPresentToUser() -> String { + var localizedDescription: String { switch self { case let .custom(description): return description @@ -29,4 +23,8 @@ extension NetworkError { return R.string.localizable.invalidInput() } } + + public init(description: String) { + self = .custom(description: description) + } } diff --git a/Modules/Network/Extensions/URLComponents+Endpoint.swift b/Modules/Network/Extensions/URLComponents+Endpoint.swift index 0e08a34..4b1ace0 100644 --- a/Modules/Network/Extensions/URLComponents+Endpoint.swift +++ b/Modules/Network/Extensions/URLComponents+Endpoint.swift @@ -1,7 +1,7 @@ import Foundation extension URLComponents { - init?(endpoint: EndpointProtocol) { + init?(endpoint: EndpointDescriptor) { self.init() scheme = endpoint.scheme host = endpoint.host diff --git a/Modules/Network/Extensions/URLRequest+Endpoint.swift b/Modules/Network/Extensions/URLRequest+Endpoint.swift index d23bfdc..23496f8 100644 --- a/Modules/Network/Extensions/URLRequest+Endpoint.swift +++ b/Modules/Network/Extensions/URLRequest+Endpoint.swift @@ -1,7 +1,7 @@ import Foundation extension URLRequest { - init?(endpoint: EndpointProtocol) { + init?(endpoint: EndpointDescriptor) { guard let urlComponents = URLComponents(endpoint: endpoint), let url = urlComponents.url diff --git a/Modules/Network/Protocols/EndpointProtocol.swift b/Modules/Network/Protocols/EndpointDescriptor.swift similarity index 90% rename from Modules/Network/Protocols/EndpointProtocol.swift rename to Modules/Network/Protocols/EndpointDescriptor.swift index 1a5ae89..fcb78c8 100644 --- a/Modules/Network/Protocols/EndpointProtocol.swift +++ b/Modules/Network/Protocols/EndpointDescriptor.swift @@ -4,7 +4,7 @@ public typealias Header = [String: String] public typealias Parameters = [String: Any] public typealias Body = [String: Any] -public protocol EndpointProtocol { +public protocol EndpointDescriptor { var scheme: String { get } var host: String { get } var path: String { get } diff --git a/Modules/Network/Protocols/NetworkProviderProtocol.swift b/Modules/Network/Protocols/NetworkProviderProtocol.swift index 14854f5..68d1a4a 100644 --- a/Modules/Network/Protocols/NetworkProviderProtocol.swift +++ b/Modules/Network/Protocols/NetworkProviderProtocol.swift @@ -3,5 +3,5 @@ import Foundation public typealias ResultHandler = (Result) -> Void public protocol NetworkProviderProtocol { - func performRequest(endpoint: EndpointProtocol, result: @escaping ResultHandler) + func performRequest(endpoint: EndpointDescriptor, result: @escaping ResultHandler) } diff --git a/Modules/Network/Protocols/TreatJSONDecode.swift b/Modules/Network/Protocols/TreatJSONDecode.swift index d9d30e6..a180f5c 100644 --- a/Modules/Network/Protocols/TreatJSONDecode.swift +++ b/Modules/Network/Protocols/TreatJSONDecode.swift @@ -11,22 +11,17 @@ public extension TreatJSONDecode { let decodable = try decoder.decode(T.self, from: data) result(.success(decodable)) } catch DecodingError.keyNotFound(let key, let context) { - result(.failure( - NetworkError(description: - "Failed to decode missing key '\(key.stringValue)' not found – \(context.debugDescription)") - ) - ) + let description = """ + Failed to decode missing key '\(key.stringValue)' + not found – \(context.debugDescription) + """ + result(.failure(NetworkError(description: description))) } catch DecodingError.typeMismatch(_, let context) { - result(.failure( - NetworkError(description: "Failed to decode to type mismatch – \(context.debugDescription)") - ) - ) + let description = "Failed to decode to type mismatch – \(context.debugDescription)" + result(.failure(NetworkError(description: description))) } catch DecodingError.valueNotFound(let type, let context) { - result(.failure( - NetworkError( - description: "Failed to decode to missing \(type) value – \(context.debugDescription)") - ) - ) + let descritpion = "Failed to decode to missing \(type) value – \(context.debugDescription)" + result(.failure(NetworkError(description: descritpion))) } catch DecodingError.dataCorrupted(_) { result(.failure(.noJSONData)) } catch { diff --git a/Modules/Network/Providers/URLSessionProvider.swift b/Modules/Network/Providers/URLSessionProvider.swift index dfb3ccd..fd7cf82 100644 --- a/Modules/Network/Providers/URLSessionProvider.swift +++ b/Modules/Network/Providers/URLSessionProvider.swift @@ -7,10 +7,7 @@ public final class URLSessionProvider: NetworkProviderProtocol, RequestHandleRes session = URLSession(configuration: URLSession.shared.configuration) } - public func performRequest( - endpoint: EndpointProtocol, - result: @escaping ResultHandler - ) { + public func performRequest(endpoint: EndpointDescriptor, result: @escaping ResultHandler) { guard let request = URLRequest(endpoint: endpoint) else { preconditionFailure("Fail on create request") } let task = session.dataTask(with: request) { data, response, error in diff --git a/iddog.xcodeproj/project.pbxproj b/iddog.xcodeproj/project.pbxproj index e7f36e8..dcd9f5c 100644 --- a/iddog.xcodeproj/project.pbxproj +++ b/iddog.xcodeproj/project.pbxproj @@ -36,7 +36,7 @@ 796AC0274D4E0B5C91DF2F5CCB3671CD /* CredentialsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A203503D8B2A731609828777C7E9BD0D /* CredentialsProtocol.swift */; }; 1F6693164FE4FF9D31EF83F62DA60E8C /* CustomFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64B3031394E9C4A2D8BF3FB9C3C7B0B /* CustomFont.swift */; }; E05334BC999615FC1520FBEFCAA20D72 /* Endpoint+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3535F1CF753D03802C01A6D3048137A5 /* Endpoint+Defaults.swift */; }; - CFB32A58AA2134503EC3B9DA0016BDF4 /* EndpointProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CED61EE95AC5230F4B32B2BFF8591B /* EndpointProtocol.swift */; }; + 4564FD0DC18E1B37F645D8A00A6EA29C /* EndpointDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7B633C84D136B2B30FF485E2AEDD44 /* EndpointDescriptor.swift */; }; B3AF852B2A857D63D5FA44E6601DEDDB /* Fillable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42219D8DCF1A59602B23AB10493B526F /* Fillable.swift */; }; 114D9900FC1C6FEC1CB6E59EAF33DFBB /* FontHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69CD5FECFC7FDB4A4A3E76B1435A3822 /* FontHelper.swift */; }; 54E1AC4CDD7D22B43C1F37164492F127 /* GalleryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC8A60F35403639B145334A2147504A0 /* GalleryCollectionViewCell.swift */; }; @@ -71,8 +71,10 @@ ECB7A241FC44E3F17D6487612F203411 /* LoginConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C466E6B0281219DED8C4BC03299BBA8 /* LoginConstants.swift */; }; 912B556EBD312D8EEACFA7A1DF5FE334 /* LoginInteractor+LoginBusinessLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B2D1A2241F09A87AF300C602E4622D /* LoginInteractor+LoginBusinessLogic.swift */; }; 2A5439A3EBA4DC549F23468636EB4258 /* LoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B269594EF24B14C4E0D1189CEB42C3 /* LoginInteractor.swift */; }; + 6E359991B506BBC84FB4F9873B6A7638 /* LoginInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 152BF4DE3AB8A1D132B72BF542AABFFB /* LoginInteractorTests.swift */; }; 61FAC16399442644E98EE5AFE6B2E6D5 /* LoginPresenter+LoginPresentationLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5CC788C15FF8A12B954C0CB97615B7F /* LoginPresenter+LoginPresentationLogic.swift */; }; 24C2B776634437D5343F7BC7DC853CE8 /* LoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D178B6BF12BB4804100B97D81F785C0B /* LoginPresenter.swift */; }; + AC8D3B96D09F8B073AF2237D6B4911D6 /* LoginPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842ABB275930507EF99C7350238B28D6 /* LoginPresenterTests.swift */; }; 7722FBBE8E83F9CE2D4AD8DF94EAAC16 /* LoginRouter+LoginDataPassing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498CFAB0A0602AE64674FB7F7E2AE2E5 /* LoginRouter+LoginDataPassing.swift */; }; E5E9B6B31F3D5F4FA830790A574A912D /* LoginRouter+LoginRoutingLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C03478AB30FCF33F3C0F62E3EB5EC6A /* LoginRouter+LoginRoutingLogic.swift */; }; 57389DBA3BC61864EB388B7F8E722749 /* LoginRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E038F3CCCCF903CB75F6971190C3CE61 /* LoginRouter.swift */; }; @@ -80,8 +82,10 @@ 220663271FC63D3C8F3BA72FBEAD0F61 /* LoginViewController+LoginDisplayLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE34DAA063F80CF252407322025B643 /* LoginViewController+LoginDisplayLogic.swift */; }; 6A425B99EFE5E953A0B19B485B6BFB46 /* LoginViewController+TextFieldDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE6B10BC22C6A417C3D067A604490650 /* LoginViewController+TextFieldDelegate.swift */; }; EFA8DFF0092FBC675F95CB0FA599AF58 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF594494F12BB8AA41400DEE2FD02F6 /* LoginViewController.swift */; }; + EDE3ABF22B9F62147E6B928C1ED362DB /* LoginViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799D649ECB8FAE6CA10DEFEBA72DF5FE /* LoginViewControllerTests.swift */; }; D552E44540D046FE891A70500049C55F /* LoginWorker+LoginExternalCalls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785B14193ECFB5017CF513756A072A5D /* LoginWorker+LoginExternalCalls.swift */; }; B8F892BFEC498A440AB548A28C377565 /* LoginWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3685B385D3E7584C95BE4F1EDFAB81E9 /* LoginWorker.swift */; }; + 21C6EAA6240BD5E96B23FEC08A743A6E /* LoginWorkerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F440E50A00B197717C554094041B7D1C /* LoginWorkerTests.swift */; }; 4518B69FAC1E103055FE3232A65F3002 /* MainInteractor+MainBusinessLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9792B5D20744770669B87A360F4E6D17 /* MainInteractor+MainBusinessLogic.swift */; }; FE84EB3F34DEC257AA64A781C57921A4 /* MainInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2386E2448C7BB688A724822A1B687E /* MainInteractor.swift */; }; 61377143D328F3D94922AB3E8479DE27 /* MainPresenter+PresentationLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0B6801C5A673942D460B0E62628AB8 /* MainPresenter+PresentationLogic.swift */; }; @@ -130,7 +134,7 @@ D1B234830F4753215653B0D4E019D141 /* URLSessionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498B61BEC1A28039563B00E71CCD39DB /* URLSessionProvider.swift */; }; CE0857E415EF44104395B76D64D5F2EC /* UserModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A6B32D67C1C571811894764DBE7123 /* UserModel.swift */; }; A2692F2886315A4D6744CCEC2632B1F3 /* UserProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59DA7DD22787EBA87D315C63F2D3D4E /* UserProtocol.swift */; }; - 8E5325152CBEE733819958AA96DAE15B /* ViewCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648E680A86CF931813E0A9FBBBBF54E4 /* ViewCodable.swift */; }; + C65FF30D589045D6C080F867B38FE4E0 /* ViewCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4321CFEC00A2F82A9BA0DE1C33C5AE41 /* ViewCoding.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -181,7 +185,7 @@ A203503D8B2A731609828777C7E9BD0D /* CredentialsProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialsProtocol.swift; sourceTree = ""; }; E64B3031394E9C4A2D8BF3FB9C3C7B0B /* CustomFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomFont.swift; sourceTree = ""; }; 3535F1CF753D03802C01A6D3048137A5 /* Endpoint+Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Endpoint+Defaults.swift"; sourceTree = ""; }; - 95CED61EE95AC5230F4B32B2BFF8591B /* EndpointProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointProtocol.swift; sourceTree = ""; }; + EC7B633C84D136B2B30FF485E2AEDD44 /* EndpointDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointDescriptor.swift; sourceTree = ""; }; 42219D8DCF1A59602B23AB10493B526F /* Fillable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fillable.swift; sourceTree = ""; }; 69CD5FECFC7FDB4A4A3E76B1435A3822 /* FontHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontHelper.swift; sourceTree = ""; }; FC8A60F35403639B145334A2147504A0 /* GalleryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryCollectionViewCell.swift; sourceTree = ""; }; @@ -218,8 +222,10 @@ 8C466E6B0281219DED8C4BC03299BBA8 /* LoginConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginConstants.swift; sourceTree = ""; }; 52B2D1A2241F09A87AF300C602E4622D /* LoginInteractor+LoginBusinessLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginInteractor+LoginBusinessLogic.swift"; sourceTree = ""; }; 54B269594EF24B14C4E0D1189CEB42C3 /* LoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInteractor.swift; sourceTree = ""; }; + 152BF4DE3AB8A1D132B72BF542AABFFB /* LoginInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInteractorTests.swift; sourceTree = ""; }; F5CC788C15FF8A12B954C0CB97615B7F /* LoginPresenter+LoginPresentationLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginPresenter+LoginPresentationLogic.swift"; sourceTree = ""; }; D178B6BF12BB4804100B97D81F785C0B /* LoginPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresenter.swift; sourceTree = ""; }; + 842ABB275930507EF99C7350238B28D6 /* LoginPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresenterTests.swift; sourceTree = ""; }; 498CFAB0A0602AE64674FB7F7E2AE2E5 /* LoginRouter+LoginDataPassing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginRouter+LoginDataPassing.swift"; sourceTree = ""; }; 0C03478AB30FCF33F3C0F62E3EB5EC6A /* LoginRouter+LoginRoutingLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginRouter+LoginRoutingLogic.swift"; sourceTree = ""; }; E038F3CCCCF903CB75F6971190C3CE61 /* LoginRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRouter.swift; sourceTree = ""; }; @@ -227,8 +233,10 @@ 2BE34DAA063F80CF252407322025B643 /* LoginViewController+LoginDisplayLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginViewController+LoginDisplayLogic.swift"; sourceTree = ""; }; EE6B10BC22C6A417C3D067A604490650 /* LoginViewController+TextFieldDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginViewController+TextFieldDelegate.swift"; sourceTree = ""; }; 1FF594494F12BB8AA41400DEE2FD02F6 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + 799D649ECB8FAE6CA10DEFEBA72DF5FE /* LoginViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewControllerTests.swift; sourceTree = ""; }; 785B14193ECFB5017CF513756A072A5D /* LoginWorker+LoginExternalCalls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginWorker+LoginExternalCalls.swift"; sourceTree = ""; }; 3685B385D3E7584C95BE4F1EDFAB81E9 /* LoginWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginWorker.swift; sourceTree = ""; }; + F440E50A00B197717C554094041B7D1C /* LoginWorkerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginWorkerTests.swift; sourceTree = ""; }; 9792B5D20744770669B87A360F4E6D17 /* MainInteractor+MainBusinessLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainInteractor+MainBusinessLogic.swift"; sourceTree = ""; }; 1D2386E2448C7BB688A724822A1B687E /* MainInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainInteractor.swift; sourceTree = ""; }; 7B0B6801C5A673942D460B0E62628AB8 /* MainPresenter+PresentationLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainPresenter+PresentationLogic.swift"; sourceTree = ""; }; @@ -281,7 +289,7 @@ 498B61BEC1A28039563B00E71CCD39DB /* URLSessionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionProvider.swift; sourceTree = ""; }; D7A6B32D67C1C571811894764DBE7123 /* UserModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserModel.swift; sourceTree = ""; }; D59DA7DD22787EBA87D315C63F2D3D4E /* UserProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProtocol.swift; sourceTree = ""; }; - 648E680A86CF931813E0A9FBBBBF54E4 /* ViewCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCodable.swift; sourceTree = ""; }; + 4321CFEC00A2F82A9BA0DE1C33C5AE41 /* ViewCoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCoding.swift; sourceTree = ""; }; 8BE207F3EF7AD794CC026E166D72C67C /* iddog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iddog.app; sourceTree = BUILT_PRODUCTS_DIR; }; 710A87040F43C139EFD6A0C484AE302A /* iddogTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iddogTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E58245178D1F5546425B07F3283A7250 /* iddogUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iddogUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -323,6 +331,14 @@ path = View; sourceTree = ""; }; + 03C1CE289BDB7DE920021C4866174813 /* Scenes */ = { + isa = PBXGroup; + children = ( + 5D5927DEAADBF23830C5F5978F59894D /* Login */, + ); + path = Scenes; + sourceTree = ""; + }; 04FE9463180B35E35FA55A43E9503FE6 /* Strings */ = { isa = PBXGroup; children = ( @@ -378,6 +394,7 @@ 15D5F0115B4CD96BED4189D85F67F671 /* iddogTests */ = { isa = PBXGroup; children = ( + 03C1CE289BDB7DE920021C4866174813 /* Scenes */, D76FEDCD57474FD00D2FDDFF27D61EEE /* IDDogTests.swift */, 0369764C9C55EEE2A52445B8F1544F77 /* Info.plist */, ); @@ -406,7 +423,7 @@ 80F4E84DA9CD237675191EE3D7FD7942 /* Identifiable.swift */, 0FFF98F37E4A6236A17ED363A8601F93 /* RootSceneType.swift */, E906C5F4D1E2712E36B346491D7B6180 /* RouterShowAlert.swift */, - 648E680A86CF931813E0A9FBBBBF54E4 /* ViewCodable.swift */, + 4321CFEC00A2F82A9BA0DE1C33C5AE41 /* ViewCoding.swift */, ); path = Protocols; sourceTree = ""; @@ -596,6 +613,17 @@ path = Commons; sourceTree = ""; }; + 5D5927DEAADBF23830C5F5978F59894D /* Login */ = { + isa = PBXGroup; + children = ( + 152BF4DE3AB8A1D132B72BF542AABFFB /* LoginInteractorTests.swift */, + 842ABB275930507EF99C7350238B28D6 /* LoginPresenterTests.swift */, + 799D649ECB8FAE6CA10DEFEBA72DF5FE /* LoginViewControllerTests.swift */, + F440E50A00B197717C554094041B7D1C /* LoginWorkerTests.swift */, + ); + path = Login; + sourceTree = ""; + }; 66614CFBE927993AD6B06B5E2B285DA2 /* Properties */ = { isa = PBXGroup; children = ( @@ -860,7 +888,7 @@ C33E9F4BAFADC69B4561FDC4AF427AE4 /* Protocols */ = { isa = PBXGroup; children = ( - 95CED61EE95AC5230F4B32B2BFF8591B /* EndpointProtocol.swift */, + EC7B633C84D136B2B30FF485E2AEDD44 /* EndpointDescriptor.swift */, AAA82687F76EC9F23AD123F76B5B6E13 /* NetworkProviderProtocol.swift */, 1381D1282BFEAD1DEC61DC8ABCE7AFDB /* RequestHandleResponsable.swift */, BB1637B6FE9778397E9B252BF0398F9B /* TreatDataResponse.swift */, @@ -1352,7 +1380,7 @@ 796AC0274D4E0B5C91DF2F5CCB3671CD /* CredentialsProtocol.swift in Sources */, 1F6693164FE4FF9D31EF83F62DA60E8C /* CustomFont.swift in Sources */, E05334BC999615FC1520FBEFCAA20D72 /* Endpoint+Defaults.swift in Sources */, - CFB32A58AA2134503EC3B9DA0016BDF4 /* EndpointProtocol.swift in Sources */, + 4564FD0DC18E1B37F645D8A00A6EA29C /* EndpointDescriptor.swift in Sources */, B3AF852B2A857D63D5FA44E6601DEDDB /* Fillable.swift in Sources */, 114D9900FC1C6FEC1CB6E59EAF33DFBB /* FontHelper.swift in Sources */, 54E1AC4CDD7D22B43C1F37164492F127 /* GalleryCollectionViewCell.swift in Sources */, @@ -1440,7 +1468,7 @@ D1B234830F4753215653B0D4E019D141 /* URLSessionProvider.swift in Sources */, CE0857E415EF44104395B76D64D5F2EC /* UserModel.swift in Sources */, A2692F2886315A4D6744CCEC2632B1F3 /* UserProtocol.swift in Sources */, - 8E5325152CBEE733819958AA96DAE15B /* ViewCodable.swift in Sources */, + C65FF30D589045D6C080F867B38FE4E0 /* ViewCoding.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1457,6 +1485,10 @@ buildActionMask = 2147483647; files = ( 33A89CCA2FD037FA33F5BBCDA8C72DDD /* IDDogTests.swift in Sources */, + 6E359991B506BBC84FB4F9873B6A7638 /* LoginInteractorTests.swift in Sources */, + AC8D3B96D09F8B073AF2237D6B4911D6 /* LoginPresenterTests.swift in Sources */, + EDE3ABF22B9F62147E6B928C1ED362DB /* LoginViewControllerTests.swift in Sources */, + 21C6EAA6240BD5E96B23FEC08A743A6E /* LoginWorkerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iddog.xcodeproj/xcshareddata/xcschemes/iddog.xcscheme b/iddog.xcodeproj/xcshareddata/xcschemes/iddog.xcscheme new file mode 100644 index 0000000..d78d89a --- /dev/null +++ b/iddog.xcodeproj/xcshareddata/xcschemes/iddog.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iddog.xcodeproj/xcuserdata/rafael.ziliao.xcuserdatad/xcschemes/xcschememanagement.plist b/iddog.xcodeproj/xcuserdata/rafael.ziliao.xcuserdatad/xcschemes/xcschememanagement.plist index e49d2ef..324531b 100644 --- a/iddog.xcodeproj/xcuserdata/rafael.ziliao.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/iddog.xcodeproj/xcuserdata/rafael.ziliao.xcuserdatad/xcschemes/xcschememanagement.plist @@ -10,5 +10,23 @@ 7 + SuppressBuildableAutocreation + + 1C5CD6FB2614574C26243A2C5AC946A3 + + primary + + + 58A8675E20CD5D98AE815D5B9B24B4BB + + primary + + + AB28751F497A08AA67901630FE8903F8 + + primary + + + diff --git a/iddog.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/iddog.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/iddog.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/iddog/Sources/IDDogAPI/Endpoints/Endpoint+Defaults.swift b/iddog/Sources/IDDogAPI/Endpoints/Endpoint+Defaults.swift index 715db15..0dfffa0 100644 --- a/iddog/Sources/IDDogAPI/Endpoints/Endpoint+Defaults.swift +++ b/iddog/Sources/IDDogAPI/Endpoints/Endpoint+Defaults.swift @@ -1,6 +1,6 @@ import Foundation -extension EndpointProtocol { +extension EndpointDescriptor { var scheme: String { return "https" } diff --git a/iddog/Sources/IDDogAPI/Endpoints/GalleryEndpoint.swift b/iddog/Sources/IDDogAPI/Endpoints/GalleryEndpoint.swift index 6fc881e..91b7b96 100644 --- a/iddog/Sources/IDDogAPI/Endpoints/GalleryEndpoint.swift +++ b/iddog/Sources/IDDogAPI/Endpoints/GalleryEndpoint.swift @@ -1,6 +1,6 @@ import Foundation -struct GalleryEndpoint: EndpointProtocol { +struct GalleryEndpoint: EndpointDescriptor { var path: String { return "/feed" } diff --git a/iddog/Sources/IDDogAPI/Endpoints/SignupEndpoint.swift b/iddog/Sources/IDDogAPI/Endpoints/SignupEndpoint.swift index a039e63..6461c9b 100644 --- a/iddog/Sources/IDDogAPI/Endpoints/SignupEndpoint.swift +++ b/iddog/Sources/IDDogAPI/Endpoints/SignupEndpoint.swift @@ -1,6 +1,6 @@ import Foundation -struct SignupEndpoint: EndpointProtocol { +struct SignupEndpoint: EndpointDescriptor { var path: String { return "/signup" } diff --git a/iddog/Sources/Scenes/Categories/View/CategoriesViewController.swift b/iddog/Sources/Scenes/Categories/View/CategoriesViewController.swift index 5a6823e..6ea4ce7 100644 --- a/iddog/Sources/Scenes/Categories/View/CategoriesViewController.swift +++ b/iddog/Sources/Scenes/Categories/View/CategoriesViewController.swift @@ -77,7 +77,7 @@ extension CategoriesViewController { } } -extension CategoriesViewController: ViewCodable { +extension CategoriesViewController: ViewCoding { func buildViewHierarchy() { view.addSubview(tableView) } diff --git a/iddog/Sources/Scenes/Gallery/View/Cell/Interactor/GalleryCollectionViewCellInteractor.swift b/iddog/Sources/Scenes/Gallery/View/Cell/Interactor/GalleryCollectionViewCellInteractor.swift index b410d38..8db48f2 100644 --- a/iddog/Sources/Scenes/Gallery/View/Cell/Interactor/GalleryCollectionViewCellInteractor.swift +++ b/iddog/Sources/Scenes/Gallery/View/Cell/Interactor/GalleryCollectionViewCellInteractor.swift @@ -2,6 +2,7 @@ import Foundation protocol GalleryCollectionViewCellBusinessLogic { func downloadImage(from url: URL) + func cancelDownload() } final class GalleryCollectionViewCellInteractor { @@ -20,4 +21,8 @@ extension GalleryCollectionViewCellInteractor: GalleryCollectionViewCellBusiness } } } + + func cancelDownload() { + worker?.cancelDownload() + } } diff --git a/iddog/Sources/Scenes/Gallery/View/Cell/View/GalleryCollectionViewCell.swift b/iddog/Sources/Scenes/Gallery/View/Cell/View/GalleryCollectionViewCell.swift index ee83bd5..f08ee2c 100644 --- a/iddog/Sources/Scenes/Gallery/View/Cell/View/GalleryCollectionViewCell.swift +++ b/iddog/Sources/Scenes/Gallery/View/Cell/View/GalleryCollectionViewCell.swift @@ -26,6 +26,11 @@ class GalleryCollectionViewCell: UICollectionViewCell, Identifiable { fatalError("init(coder:) has not been implemented") } + override func prepareForReuse() { + super.prepareForReuse() + interactor?.cancelDownload() + } + private func setupCellArchitecture() { let view = self let interactor = GalleryCollectionViewCellInteractor() @@ -41,7 +46,7 @@ class GalleryCollectionViewCell: UICollectionViewCell, Identifiable { } } -extension GalleryCollectionViewCell: ViewCodable { +extension GalleryCollectionViewCell: ViewCoding { func buildViewHierarchy() { contentView.addSubview(imageView) } @@ -52,8 +57,6 @@ extension GalleryCollectionViewCell: ViewCodable { imageView.widthConstraintEqualTo(parentView: contentView) imageView.heightConstraintEqualTo(parentView: contentView) } - - func additionalSetup() {} } extension GalleryCollectionViewCell: Fillable { diff --git a/iddog/Sources/Scenes/Gallery/View/Cell/Worker/GalleryCollectionViewCellWorker.swift b/iddog/Sources/Scenes/Gallery/View/Cell/Worker/GalleryCollectionViewCellWorker.swift index 19efb5a..0c6428a 100644 --- a/iddog/Sources/Scenes/Gallery/View/Cell/Worker/GalleryCollectionViewCellWorker.swift +++ b/iddog/Sources/Scenes/Gallery/View/Cell/Worker/GalleryCollectionViewCellWorker.swift @@ -2,6 +2,7 @@ import Foundation protocol GalleryCollectionViewCellExternalCalls { func fetchImage(from url: URL, result: @escaping ResulImageHandler) + func cancelDownload() } final class GalleryCollectionViewCellWorker { @@ -17,7 +18,7 @@ final class GalleryCollectionViewCellWorker { extension GalleryCollectionViewCellWorker: GalleryCollectionViewCellExternalCalls { func fetchImage(from url: URL, result: @escaping ResulImageHandler) { - imageLoaderService.loadImage(with: url) { response in + imageLoaderService.downloadImage(with: url) { response in switch response { case let .success(image): result(.success(image)) @@ -26,4 +27,8 @@ extension GalleryCollectionViewCellWorker: GalleryCollectionViewCellExternalCall } } } + + func cancelDownload() { + imageLoaderService.cancelDownload() + } } diff --git a/iddog/Sources/Scenes/Gallery/View/GalleryViewController+CollectionViewDataSource.swift b/iddog/Sources/Scenes/Gallery/View/GalleryViewController+CollectionViewDataSource.swift index fa9b0e4..124e86f 100644 --- a/iddog/Sources/Scenes/Gallery/View/GalleryViewController+CollectionViewDataSource.swift +++ b/iddog/Sources/Scenes/Gallery/View/GalleryViewController+CollectionViewDataSource.swift @@ -1,15 +1,14 @@ import UIKit extension GalleryViewController: UICollectionViewDataSource { - func collectionView( - _ collectionView: UICollectionView, - numberOfItemsInSection section: Int - ) -> Int { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return galleryData?.list.count ?? .zero } - func collectionView(_ collectionView: UICollectionView, - cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + func collectionView( + _ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath + ) -> UICollectionViewCell { let cell: GalleryCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) cell.fill(with: galleryData!.list[indexPath.row]) diff --git a/iddog/Sources/Scenes/Gallery/View/GalleryViewController.swift b/iddog/Sources/Scenes/Gallery/View/GalleryViewController.swift index 860cfd2..2a49d57 100644 --- a/iddog/Sources/Scenes/Gallery/View/GalleryViewController.swift +++ b/iddog/Sources/Scenes/Gallery/View/GalleryViewController.swift @@ -64,7 +64,7 @@ final class GalleryViewController: UIViewController { } } -extension GalleryViewController: ViewCodable { +extension GalleryViewController: ViewCoding { func buildViewHierarchy() { view.addSubview(collectionView) } diff --git a/iddog/Sources/Scenes/Login/Interactor/LoginInteractor+LoginBusinessLogic.swift b/iddog/Sources/Scenes/Login/Interactor/LoginInteractor+LoginBusinessLogic.swift index 6844a01..12a8f39 100644 --- a/iddog/Sources/Scenes/Login/Interactor/LoginInteractor+LoginBusinessLogic.swift +++ b/iddog/Sources/Scenes/Login/Interactor/LoginInteractor+LoginBusinessLogic.swift @@ -9,6 +9,7 @@ extension LoginInteractor: LoginBusinessLogic { worker?.signUp(with: email) { [weak self]response in switch response { case let .success(credentials): + self?.worker?.storeAccessToken(credentials.user.token) self?.presenter?.presentCategories(user: credentials.user) case let .failure(error): self?.presenter?.presentSignUpError(error) diff --git a/iddog/Sources/Scenes/Login/View/LoginViewController.swift b/iddog/Sources/Scenes/Login/View/LoginViewController.swift index 165a8c0..ea912c3 100644 --- a/iddog/Sources/Scenes/Login/View/LoginViewController.swift +++ b/iddog/Sources/Scenes/Login/View/LoginViewController.swift @@ -97,14 +97,14 @@ final class LoginViewController: UIViewController { } } -private extension LoginViewController { +extension LoginViewController { @objc func didTapSignUpButton(sender: Any) { showLoading(true) interactor?.signUp(with: emailTextField.text) } } -extension LoginViewController: ViewCodable { +extension LoginViewController: ViewCoding { func buildViewHierarchy() { view.addSubview(appNameLabel) view.addSubview(contentStackView) diff --git a/iddog/Sources/Scenes/Login/Worker/LoginWorker+LoginExternalCalls.swift b/iddog/Sources/Scenes/Login/Worker/LoginWorker+LoginExternalCalls.swift index 28d2fb1..5609b66 100644 --- a/iddog/Sources/Scenes/Login/Worker/LoginWorker+LoginExternalCalls.swift +++ b/iddog/Sources/Scenes/Login/Worker/LoginWorker+LoginExternalCalls.swift @@ -2,9 +2,14 @@ import Foundation protocol LoginExternalCalls { func signUp(with email: String?, result: @escaping ResultHandler) + func storeAccessToken(_ token: String) } extension LoginWorker: LoginExternalCalls { + func storeAccessToken(_ token: String) { + credentialStorage.setAccessTokenAsync(token) + } + func signUp(with email: String?, result: @escaping ResultHandler) { guard let email = email else { result(.failure(.unauthorized)) @@ -13,20 +18,13 @@ extension LoginWorker: LoginExternalCalls { let signUpEndpoint = SignupEndpoint(email: email) - let result: ResultHandler = { [weak self] response in - switch response { + network.performRequest(endpoint: signUpEndpoint) { (value: Result) in + switch value { case let .success(credentials): - self?.credentialStorage.setAccessTokenAsync(credentials.user.token) result(.success(credentials)) - case let .failure(error): + case let .failure(error): result(.failure(error)) } } - - network.performRequest( - endpoint: signUpEndpoint, - result: result - ) - } } diff --git a/iddog/Sources/Scenes/Main/View/MainViewController.swift b/iddog/Sources/Scenes/Main/View/MainViewController.swift index 122f1c7..3a513d6 100644 --- a/iddog/Sources/Scenes/Main/View/MainViewController.swift +++ b/iddog/Sources/Scenes/Main/View/MainViewController.swift @@ -53,7 +53,7 @@ private extension MainViewController { } } -extension MainViewController: ViewCodable { +extension MainViewController: ViewCoding { func buildViewHierarchy() { view.addSubview(appNameLabel) view.addSubview(authorLabel) diff --git a/iddog/Sources/Scenes/Photo Detail/View/PhotoDetailViewController.swift b/iddog/Sources/Scenes/Photo Detail/View/PhotoDetailViewController.swift index 54f21ae..8f31f5e 100644 --- a/iddog/Sources/Scenes/Photo Detail/View/PhotoDetailViewController.swift +++ b/iddog/Sources/Scenes/Photo Detail/View/PhotoDetailViewController.swift @@ -13,10 +13,7 @@ final class PhotoDetailViewController: UIViewController { // MARK: Object lifecycle - override init( - nibName nibNameOrNil: String?, - bundle nibBundleOrNil: Bundle? - ) { + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } @@ -42,7 +39,7 @@ final class PhotoDetailViewController: UIViewController { } } -extension PhotoDetailViewController: ViewCodable { +extension PhotoDetailViewController: ViewCoding { func buildViewHierarchy() { view.addSubview(imageView) } diff --git a/iddog/Sources/Scenes/Photo Detail/Worker/PhotoDetailWorker+PhotoDetailExternalCalls.swift b/iddog/Sources/Scenes/Photo Detail/Worker/PhotoDetailWorker+PhotoDetailExternalCalls.swift index b8540cc..b0f0c80 100644 --- a/iddog/Sources/Scenes/Photo Detail/Worker/PhotoDetailWorker+PhotoDetailExternalCalls.swift +++ b/iddog/Sources/Scenes/Photo Detail/Worker/PhotoDetailWorker+PhotoDetailExternalCalls.swift @@ -6,7 +6,7 @@ protocol PhotoDetailExternalCalls { extension PhotoDetailWorker: PhotoDetailExternalCalls { func fetchImage(from url: URL, result: @escaping ResulImageHandler) { - imageLoaderService.loadImage(with: url) { response in + imageLoaderService.downloadImage(with: url) { response in switch response { case let .success(image): result(.success(image)) diff --git a/iddog/Sources/Scenes/Photo Detail/Worker/PhotoDetailWorker.swift b/iddog/Sources/Scenes/Photo Detail/Worker/PhotoDetailWorker.swift index 045ff6c..66f7d8a 100644 --- a/iddog/Sources/Scenes/Photo Detail/Worker/PhotoDetailWorker.swift +++ b/iddog/Sources/Scenes/Photo Detail/Worker/PhotoDetailWorker.swift @@ -3,9 +3,7 @@ import Foundation final class PhotoDetailWorker { let imageLoaderService: ImageLoaderServiceProtocol - init( - imageLoaderService: ImageLoaderServiceProtocol - ) { + init(imageLoaderService: ImageLoaderServiceProtocol) { self.imageLoaderService = imageLoaderService } } diff --git a/iddogTests/Scenes/Login/LoginInteractorTests.swift b/iddogTests/Scenes/Login/LoginInteractorTests.swift new file mode 100644 index 0000000..d53b621 --- /dev/null +++ b/iddogTests/Scenes/Login/LoginInteractorTests.swift @@ -0,0 +1,117 @@ +@testable import iddog +import XCTest + +class LoginInteractorTests: XCTestCase { + // MARK: Subject under test + + var sut: LoginInteractor! + + // MARK: Test lifecycle + + override func setUp() { + super.setUp() + setupLoginInteractor() + } + + override func tearDown() { + super.tearDown() + } + + // MARK: Test setup + + func setupLoginInteractor() { + sut = LoginInteractor() + } + + // MARK: Test doubles + + class LoginPresentationLogicSpy: LoginPresentationLogic { + var presentCategoriesCalled = false + var presentSignUpErrorCalled = false + + func presentCategories(user: UserProtocol) { + presentCategoriesCalled = true + } + + func presentSignUpError(_ error: NetworkError) { + presentSignUpErrorCalled = true + } + + } + + class LoginExternalCallsSpy: LoginExternalCalls { + var storeAccessTokenCalled = false + var signUpCalled = false + + func signUp(with email: String?, result: @escaping ResultHandler) { + signUpCalled = true + } + + func storeAccessToken(_ token: String) { + storeAccessTokenCalled = true + } + } + + // MARK: Tests + + func testAskLoginWorkerToSignUpAndPresentCategories() { + // Given + let email = "rafaelteste2@email.com" + + let loginPresentationLogicSpy = LoginPresentationLogicSpy() + sut.presenter = loginPresentationLogicSpy + + let loginExternalCallsSpy = LoginExternalCallsSpy() + sut.worker = loginExternalCallsSpy + + // When + sut.signUp(with: email) + + // Then + XCTAssertTrue( + loginExternalCallsSpy.signUpCalled, + "signUp(with:) should ask LoginWorker to sign up" + ) + + XCTAssertTrue( + loginExternalCallsSpy.storeAccessTokenCalled, + "signUp(with:) should ask LoginWorker to store token" + ) + + XCTAssertTrue( + loginPresentationLogicSpy.presentCategoriesCalled, + "signUp(with:) should ask the presenter to present categories" + ) + } + + func testAskLoginWorkerToSignUpAndPresentSignUpError() { + // Given + let email: String? = nil + + let loginPresentationLogicSpy = LoginPresentationLogicSpy() + sut.presenter = loginPresentationLogicSpy + + let loginExternalCallsSpy = LoginExternalCallsSpy() + sut.worker = loginExternalCallsSpy + + // When + sut.signUp(with: email) + + // Then + XCTAssertTrue( + loginExternalCallsSpy.signUpCalled, + "signUp(with:) should ask LoginWorker to sign up" + ) + + XCTAssertFalse( + loginExternalCallsSpy.storeAccessTokenCalled, + "signUp(with:) should ask LoginWorker to not store token" + ) + + XCTAssertTrue( + loginPresentationLogicSpy.presentSignUpErrorCalled, + "signUp(with:) should ask the presenter to present sign up error" + ) + } + +} diff --git a/iddogTests/Scenes/Login/LoginPresenterTests.swift b/iddogTests/Scenes/Login/LoginPresenterTests.swift new file mode 100644 index 0000000..74215f5 --- /dev/null +++ b/iddogTests/Scenes/Login/LoginPresenterTests.swift @@ -0,0 +1,52 @@ +@testable import iddog +import XCTest + +class LoginPresenterTests: XCTestCase { + // MARK: Subject under test + + var sut: LoginPresenter! + + // MARK: Test lifecycle + + override func setUp() { + super.setUp() + setupLoginPresenter() + } + + override func tearDown() { + super.tearDown() + } + + // MARK: Test setup + + func setupLoginPresenter() { + sut = LoginPresenter() + } + + // MARK: Test doubles + + class LoginDisplayLogicSpy: LoginDisplayLogic { + func displayCategories(with user: UserProtocol) { + + } + + func displaySignUpError(_ error: NetworkError) { + + } + } + + // MARK: Tests + + func testPresentSomething() { + // Given + let spy = LoginDisplayLogicSpy() + sut.viewController = spy + + + // When + + + // Then + // XCTAssertTrue(spy.displaySomethingCalled, "presentSomething(response:) should ask the view controller to display the result") + } +} diff --git a/iddogTests/Scenes/Login/LoginViewControllerTests.swift b/iddogTests/Scenes/Login/LoginViewControllerTests.swift new file mode 100644 index 0000000..dfda7bf --- /dev/null +++ b/iddogTests/Scenes/Login/LoginViewControllerTests.swift @@ -0,0 +1,115 @@ +@testable import iddog +import XCTest + +class LoginViewControllerTests: XCTestCase { + // MARK: Subject under test + + var sut: LoginViewController! + var window: UIWindow! + + // MARK: Test lifecycle + + override func setUp() { + super.setUp() + window = UIWindow() + setupLoginViewController() + } + + override func tearDown() { + window = nil + super.tearDown() + } + + // MARK: Test setup + + func setupLoginViewController() { + sut = LoginViewController() + } + + // MARK: Test doubles + + class LoginBusinessLogicSpy: LoginBusinessLogic { + var signUpCalled = false + + func signUp(with email: String?) { + signUpCalled = true + } + } + + class LoginRoutingLogicSpy: LoginRouterType { + var routeToAlertCalled = false + var dismissCalled = false + var routeToCategoriesCalled = false + + var dataStore: LoginDataStore? + + func routeToAlert(_ alert: UIAlertController) { + routeToAlertCalled = true + } + + func dismiss() { + dismissCalled = true + } + + func routeToCategories() { + routeToCategoriesCalled = true + } + } + + // MARK: Tests + + func testSignUp() { + // Given + let loginBusinessLogicSpy = LoginBusinessLogicSpy() + sut.interactor = loginBusinessLogicSpy + + // When + sut.didTapSignUpButton(sender: self) + + // Then + XCTAssertTrue( + loginBusinessLogicSpy.signUpCalled, + " should ask the interactor to do sign up" + ) + } + + func testDisplayCategories() { + // Given + let user = UserModel( + email: "rafaelteste2@email.com", + id: "5ed151d19609601c50f3296c", + token: "token", + createdAt: "teste", + updatedAt: "teste", + version: 0 + ) + + let loginRoutingLogicSpy = LoginRoutingLogicSpy() + sut.router = loginRoutingLogicSpy + + // When + sut.displayCategories(with: user) + + // Then + XCTAssertTrue( + loginRoutingLogicSpy.routeToCategoriesCalled, + "Diplay successfully categories scene" + ) + } + + func testDisplaySignUpError() { + // Given + let loginRoutingLogicSpy = LoginRoutingLogicSpy() + sut.router = loginRoutingLogicSpy + + // When + sut.displaySignUpError(.unauthorized) + + // Then + XCTAssertTrue( + loginRoutingLogicSpy.routeToAlertCalled, + "Diplay successfully error alert" + ) + } + +} diff --git a/iddogTests/Scenes/Login/LoginWorkerTests.swift b/iddogTests/Scenes/Login/LoginWorkerTests.swift new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/iddogTests/Scenes/Login/LoginWorkerTests.swift @@ -0,0 +1 @@ +