From c7203c486543471d829e705d5a2749c936956953 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Thu, 29 Jun 2023 20:21:45 -0300 Subject: [PATCH 01/27] Config inicial do projeto. Adicionando gitignore. --- .gitignore | 4 + MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 607 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../UserInterfaceState.xcuserstate | Bin 0 -> 11471 bytes .../xcschemes/xcschememanagement.plist | 14 + MyBankApp/MyBankApp/AppDelegate.swift | 36 ++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../MyBankApp/Assets.xcassets/Contents.json | 6 + .../Base.lproj/LaunchScreen.storyboard | 25 + .../MyBankApp/Base.lproj/Main.storyboard | 24 + MyBankApp/MyBankApp/Info.plist | 25 + MyBankApp/MyBankApp/SceneDelegate.swift | 52 ++ MyBankApp/MyBankApp/ViewController.swift | 19 + MyBankApp/MyBankAppTests/MyBankAppTests.swift | 36 ++ .../MyBankAppUITests/MyBankAppUITests.swift | 41 ++ .../MyBankAppUITestsLaunchTests.swift | 32 + 18 files changed, 960 insertions(+) create mode 100644 .gitignore create mode 100644 MyBankApp/MyBankApp.xcodeproj/project.pbxproj create mode 100644 MyBankApp/MyBankApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 MyBankApp/MyBankApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 MyBankApp/MyBankApp.xcodeproj/project.xcworkspace/xcuserdata/matheusfusco.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 MyBankApp/MyBankApp/AppDelegate.swift create mode 100644 MyBankApp/MyBankApp/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 MyBankApp/MyBankApp/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 MyBankApp/MyBankApp/Assets.xcassets/Contents.json create mode 100644 MyBankApp/MyBankApp/Base.lproj/LaunchScreen.storyboard create mode 100644 MyBankApp/MyBankApp/Base.lproj/Main.storyboard create mode 100644 MyBankApp/MyBankApp/Info.plist create mode 100644 MyBankApp/MyBankApp/SceneDelegate.swift create mode 100644 MyBankApp/MyBankApp/ViewController.swift create mode 100644 MyBankApp/MyBankAppTests/MyBankAppTests.swift create mode 100644 MyBankApp/MyBankAppUITests/MyBankAppUITests.swift create mode 100644 MyBankApp/MyBankAppUITests/MyBankAppUITestsLaunchTests.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..1fd98e156 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +Pods +DS_Store +.DS_Store +CleanSwift/ \ No newline at end of file diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj new file mode 100644 index 000000000..f74f998e9 --- /dev/null +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -0,0 +1,607 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */; }; + 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */; }; + 5EF1ACB22A4E47C6002EA972 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB12A4E47C6002EA972 /* ViewController.swift */; }; + 5EF1ACB52A4E47C6002EA972 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB32A4E47C6002EA972 /* Main.storyboard */; }; + 5EF1ACB72A4E47C9002EA972 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB62A4E47C9002EA972 /* Assets.xcassets */; }; + 5EF1ACBA2A4E47C9002EA972 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB82A4E47C9002EA972 /* LaunchScreen.storyboard */; }; + 5EF1ACC52A4E47C9002EA972 /* MyBankAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACC42A4E47C9002EA972 /* MyBankAppTests.swift */; }; + 5EF1ACCF2A4E47C9002EA972 /* MyBankAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACCE2A4E47C9002EA972 /* MyBankAppUITests.swift */; }; + 5EF1ACD12A4E47C9002EA972 /* MyBankAppUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACD02A4E47C9002EA972 /* MyBankAppUITestsLaunchTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 5EF1ACC12A4E47C9002EA972 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5EF1ACA22A4E47C6002EA972 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5EF1ACA92A4E47C6002EA972; + remoteInfo = MyBankApp; + }; + 5EF1ACCB2A4E47C9002EA972 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5EF1ACA22A4E47C6002EA972 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5EF1ACA92A4E47C6002EA972; + remoteInfo = MyBankApp; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyBankApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 5EF1ACB12A4E47C6002EA972 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 5EF1ACB42A4E47C6002EA972 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 5EF1ACB62A4E47C9002EA972 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 5EF1ACB92A4E47C9002EA972 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 5EF1ACBB2A4E47C9002EA972 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5EF1ACC02A4E47C9002EA972 /* MyBankAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyBankAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 5EF1ACC42A4E47C9002EA972 /* MyBankAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyBankAppTests.swift; sourceTree = ""; }; + 5EF1ACCA2A4E47C9002EA972 /* MyBankAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyBankAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 5EF1ACCE2A4E47C9002EA972 /* MyBankAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyBankAppUITests.swift; sourceTree = ""; }; + 5EF1ACD02A4E47C9002EA972 /* MyBankAppUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyBankAppUITestsLaunchTests.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5EF1ACA72A4E47C6002EA972 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EF1ACBD2A4E47C9002EA972 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EF1ACC72A4E47C9002EA972 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5EF1ACA12A4E47C6002EA972 = { + isa = PBXGroup; + children = ( + 5EF1ACAC2A4E47C6002EA972 /* MyBankApp */, + 5EF1ACC32A4E47C9002EA972 /* MyBankAppTests */, + 5EF1ACCD2A4E47C9002EA972 /* MyBankAppUITests */, + 5EF1ACAB2A4E47C6002EA972 /* Products */, + ); + sourceTree = ""; + }; + 5EF1ACAB2A4E47C6002EA972 /* Products */ = { + isa = PBXGroup; + children = ( + 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */, + 5EF1ACC02A4E47C9002EA972 /* MyBankAppTests.xctest */, + 5EF1ACCA2A4E47C9002EA972 /* MyBankAppUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 5EF1ACAC2A4E47C6002EA972 /* MyBankApp */ = { + isa = PBXGroup; + children = ( + 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */, + 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */, + 5EF1ACB12A4E47C6002EA972 /* ViewController.swift */, + 5EF1ACB32A4E47C6002EA972 /* Main.storyboard */, + 5EF1ACB62A4E47C9002EA972 /* Assets.xcassets */, + 5EF1ACB82A4E47C9002EA972 /* LaunchScreen.storyboard */, + 5EF1ACBB2A4E47C9002EA972 /* Info.plist */, + ); + path = MyBankApp; + sourceTree = ""; + }; + 5EF1ACC32A4E47C9002EA972 /* MyBankAppTests */ = { + isa = PBXGroup; + children = ( + 5EF1ACC42A4E47C9002EA972 /* MyBankAppTests.swift */, + ); + path = MyBankAppTests; + sourceTree = ""; + }; + 5EF1ACCD2A4E47C9002EA972 /* MyBankAppUITests */ = { + isa = PBXGroup; + children = ( + 5EF1ACCE2A4E47C9002EA972 /* MyBankAppUITests.swift */, + 5EF1ACD02A4E47C9002EA972 /* MyBankAppUITestsLaunchTests.swift */, + ); + path = MyBankAppUITests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5EF1ACA92A4E47C6002EA972 /* MyBankApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5EF1ACD42A4E47C9002EA972 /* Build configuration list for PBXNativeTarget "MyBankApp" */; + buildPhases = ( + 5EF1ACA62A4E47C6002EA972 /* Sources */, + 5EF1ACA72A4E47C6002EA972 /* Frameworks */, + 5EF1ACA82A4E47C6002EA972 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MyBankApp; + productName = MyBankApp; + productReference = 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */; + productType = "com.apple.product-type.application"; + }; + 5EF1ACBF2A4E47C9002EA972 /* MyBankAppTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5EF1ACD72A4E47C9002EA972 /* Build configuration list for PBXNativeTarget "MyBankAppTests" */; + buildPhases = ( + 5EF1ACBC2A4E47C9002EA972 /* Sources */, + 5EF1ACBD2A4E47C9002EA972 /* Frameworks */, + 5EF1ACBE2A4E47C9002EA972 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5EF1ACC22A4E47C9002EA972 /* PBXTargetDependency */, + ); + name = MyBankAppTests; + productName = MyBankAppTests; + productReference = 5EF1ACC02A4E47C9002EA972 /* MyBankAppTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 5EF1ACC92A4E47C9002EA972 /* MyBankAppUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5EF1ACDA2A4E47C9002EA972 /* Build configuration list for PBXNativeTarget "MyBankAppUITests" */; + buildPhases = ( + 5EF1ACC62A4E47C9002EA972 /* Sources */, + 5EF1ACC72A4E47C9002EA972 /* Frameworks */, + 5EF1ACC82A4E47C9002EA972 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5EF1ACCC2A4E47C9002EA972 /* PBXTargetDependency */, + ); + name = MyBankAppUITests; + productName = MyBankAppUITests; + productReference = 5EF1ACCA2A4E47C9002EA972 /* MyBankAppUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 5EF1ACA22A4E47C6002EA972 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1430; + LastUpgradeCheck = 1430; + TargetAttributes = { + 5EF1ACA92A4E47C6002EA972 = { + CreatedOnToolsVersion = 14.3.1; + }; + 5EF1ACBF2A4E47C9002EA972 = { + CreatedOnToolsVersion = 14.3.1; + TestTargetID = 5EF1ACA92A4E47C6002EA972; + }; + 5EF1ACC92A4E47C9002EA972 = { + CreatedOnToolsVersion = 14.3.1; + TestTargetID = 5EF1ACA92A4E47C6002EA972; + }; + }; + }; + buildConfigurationList = 5EF1ACA52A4E47C6002EA972 /* Build configuration list for PBXProject "MyBankApp" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 5EF1ACA12A4E47C6002EA972; + productRefGroup = 5EF1ACAB2A4E47C6002EA972 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5EF1ACA92A4E47C6002EA972 /* MyBankApp */, + 5EF1ACBF2A4E47C9002EA972 /* MyBankAppTests */, + 5EF1ACC92A4E47C9002EA972 /* MyBankAppUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5EF1ACA82A4E47C6002EA972 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5EF1ACBA2A4E47C9002EA972 /* LaunchScreen.storyboard in Resources */, + 5EF1ACB72A4E47C9002EA972 /* Assets.xcassets in Resources */, + 5EF1ACB52A4E47C6002EA972 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EF1ACBE2A4E47C9002EA972 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EF1ACC82A4E47C9002EA972 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5EF1ACA62A4E47C6002EA972 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5EF1ACB22A4E47C6002EA972 /* ViewController.swift in Sources */, + 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */, + 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EF1ACBC2A4E47C9002EA972 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5EF1ACC52A4E47C9002EA972 /* MyBankAppTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EF1ACC62A4E47C9002EA972 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5EF1ACD12A4E47C9002EA972 /* MyBankAppUITestsLaunchTests.swift in Sources */, + 5EF1ACCF2A4E47C9002EA972 /* MyBankAppUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 5EF1ACC22A4E47C9002EA972 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 5EF1ACA92A4E47C6002EA972 /* MyBankApp */; + targetProxy = 5EF1ACC12A4E47C9002EA972 /* PBXContainerItemProxy */; + }; + 5EF1ACCC2A4E47C9002EA972 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 5EF1ACA92A4E47C6002EA972 /* MyBankApp */; + targetProxy = 5EF1ACCB2A4E47C9002EA972 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 5EF1ACB32A4E47C6002EA972 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 5EF1ACB42A4E47C6002EA972 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 5EF1ACB82A4E47C9002EA972 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 5EF1ACB92A4E47C9002EA972 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 5EF1ACD22A4E47C9002EA972 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 5EF1ACD32A4E47C9002EA972 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 5EF1ACD52A4E47C9002EA972 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = D67XBEEN3L; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MyBankApp/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = fusco.MyBankApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 5EF1ACD62A4E47C9002EA972 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = D67XBEEN3L; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MyBankApp/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = fusco.MyBankApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 5EF1ACD82A4E47C9002EA972 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = D67XBEEN3L; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = fusco.MyBankAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MyBankApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MyBankApp"; + }; + name = Debug; + }; + 5EF1ACD92A4E47C9002EA972 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = D67XBEEN3L; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = fusco.MyBankAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MyBankApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MyBankApp"; + }; + name = Release; + }; + 5EF1ACDB2A4E47C9002EA972 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = D67XBEEN3L; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = fusco.MyBankAppUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = MyBankApp; + }; + name = Debug; + }; + 5EF1ACDC2A4E47C9002EA972 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = D67XBEEN3L; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = fusco.MyBankAppUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = MyBankApp; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 5EF1ACA52A4E47C6002EA972 /* Build configuration list for PBXProject "MyBankApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5EF1ACD22A4E47C9002EA972 /* Debug */, + 5EF1ACD32A4E47C9002EA972 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5EF1ACD42A4E47C9002EA972 /* Build configuration list for PBXNativeTarget "MyBankApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5EF1ACD52A4E47C9002EA972 /* Debug */, + 5EF1ACD62A4E47C9002EA972 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5EF1ACD72A4E47C9002EA972 /* Build configuration list for PBXNativeTarget "MyBankAppTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5EF1ACD82A4E47C9002EA972 /* Debug */, + 5EF1ACD92A4E47C9002EA972 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5EF1ACDA2A4E47C9002EA972 /* Build configuration list for PBXNativeTarget "MyBankAppUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5EF1ACDB2A4E47C9002EA972 /* Debug */, + 5EF1ACDC2A4E47C9002EA972 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 5EF1ACA22A4E47C6002EA972 /* Project object */; +} diff --git a/MyBankApp/MyBankApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/MyBankApp/MyBankApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/MyBankApp/MyBankApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/MyBankApp/MyBankApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/MyBankApp/MyBankApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/MyBankApp/MyBankApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/MyBankApp/MyBankApp.xcodeproj/project.xcworkspace/xcuserdata/matheusfusco.xcuserdatad/UserInterfaceState.xcuserstate b/MyBankApp/MyBankApp.xcodeproj/project.xcworkspace/xcuserdata/matheusfusco.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..b9374fd9079512d9fccecd15257ae98f6295a352 GIT binary patch literal 11471 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 literal 0 HcmV?d00001 diff --git a/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist b/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..d68c618e7 --- /dev/null +++ b/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + MyBankApp.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/MyBankApp/MyBankApp/AppDelegate.swift b/MyBankApp/MyBankApp/AppDelegate.swift new file mode 100644 index 000000000..50dcfa86e --- /dev/null +++ b/MyBankApp/MyBankApp/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// AppDelegate.swift +// MyBankApp +// +// Created by Matheus Fusco on 29/06/23. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/MyBankApp/MyBankApp/Assets.xcassets/AccentColor.colorset/Contents.json b/MyBankApp/MyBankApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/MyBankApp/MyBankApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MyBankApp/MyBankApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/MyBankApp/MyBankApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..13613e3ee --- /dev/null +++ b/MyBankApp/MyBankApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MyBankApp/MyBankApp/Assets.xcassets/Contents.json b/MyBankApp/MyBankApp/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/MyBankApp/MyBankApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MyBankApp/MyBankApp/Base.lproj/LaunchScreen.storyboard b/MyBankApp/MyBankApp/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..865e9329f --- /dev/null +++ b/MyBankApp/MyBankApp/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MyBankApp/MyBankApp/Base.lproj/Main.storyboard b/MyBankApp/MyBankApp/Base.lproj/Main.storyboard new file mode 100644 index 000000000..25a763858 --- /dev/null +++ b/MyBankApp/MyBankApp/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MyBankApp/MyBankApp/Info.plist b/MyBankApp/MyBankApp/Info.plist new file mode 100644 index 000000000..dd3c9afda --- /dev/null +++ b/MyBankApp/MyBankApp/Info.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/MyBankApp/MyBankApp/SceneDelegate.swift b/MyBankApp/MyBankApp/SceneDelegate.swift new file mode 100644 index 000000000..992115f9d --- /dev/null +++ b/MyBankApp/MyBankApp/SceneDelegate.swift @@ -0,0 +1,52 @@ +// +// SceneDelegate.swift +// MyBankApp +// +// Created by Matheus Fusco on 29/06/23. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/MyBankApp/MyBankApp/ViewController.swift b/MyBankApp/MyBankApp/ViewController.swift new file mode 100644 index 000000000..2ec295598 --- /dev/null +++ b/MyBankApp/MyBankApp/ViewController.swift @@ -0,0 +1,19 @@ +// +// ViewController.swift +// MyBankApp +// +// Created by Matheus Fusco on 29/06/23. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + + +} + diff --git a/MyBankApp/MyBankAppTests/MyBankAppTests.swift b/MyBankApp/MyBankAppTests/MyBankAppTests.swift new file mode 100644 index 000000000..c5c13c9f4 --- /dev/null +++ b/MyBankApp/MyBankAppTests/MyBankAppTests.swift @@ -0,0 +1,36 @@ +// +// MyBankAppTests.swift +// MyBankAppTests +// +// Created by Matheus Fusco on 29/06/23. +// + +import XCTest +@testable import MyBankApp + +final class MyBankAppTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/MyBankApp/MyBankAppUITests/MyBankAppUITests.swift b/MyBankApp/MyBankAppUITests/MyBankAppUITests.swift new file mode 100644 index 000000000..63ba75f8a --- /dev/null +++ b/MyBankApp/MyBankAppUITests/MyBankAppUITests.swift @@ -0,0 +1,41 @@ +// +// MyBankAppUITests.swift +// MyBankAppUITests +// +// Created by Matheus Fusco on 29/06/23. +// + +import XCTest + +final class MyBankAppUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/MyBankApp/MyBankAppUITests/MyBankAppUITestsLaunchTests.swift b/MyBankApp/MyBankAppUITests/MyBankAppUITestsLaunchTests.swift new file mode 100644 index 000000000..d1edb3dce --- /dev/null +++ b/MyBankApp/MyBankAppUITests/MyBankAppUITestsLaunchTests.swift @@ -0,0 +1,32 @@ +// +// MyBankAppUITestsLaunchTests.swift +// MyBankAppUITests +// +// Created by Matheus Fusco on 29/06/23. +// + +import XCTest + +final class MyBankAppUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} From 6a60960bb6bdc56002b61cc7828a93be648e0a19 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Thu, 29 Jun 2023 20:30:01 -0300 Subject: [PATCH 02/27] ajustando gitignore --- .gitignore | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 1fd98e156..ed65ec7d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,113 @@ -Pods DS_Store .DS_Store -CleanSwift/ \ No newline at end of file +CleanSwift/ + +# Created by https://www.toptal.com/developers/gitignore/api/xcode,swift,cocoapods +# Edit at https://www.toptal.com/developers/gitignore?templates=xcode,swift,cocoapods + +### CocoaPods ### +## CocoaPods GitIgnore Template + +# CocoaPods - Only use to conserve bandwidth / Save time on Pushing +# - Also handy if you have a large number of dependant pods +# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE +Pods/ + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Xcode ### + +## Xcode 8 and earlier + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno +**/xcshareddata/WorkspaceSettings.xcsettings + +# End of https://www.toptal.com/developers/gitignore/api/xcode,swift,cocoapods \ No newline at end of file From 29680d8d125e51615e358236a348a3796e2b8222 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Thu, 29 Jun 2023 22:22:29 -0300 Subject: [PATCH 03/27] adicionando podfile e pods iniciais --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 176 +++++++++++++++++- .../xcschemes/xcschememanagement.plist | 2 +- .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + MyBankApp/Podfile | 22 +++ MyBankApp/Podfile.lock | 32 ++++ 6 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 MyBankApp/MyBankApp.xcworkspace/contents.xcworkspacedata create mode 100644 MyBankApp/MyBankApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 MyBankApp/Podfile create mode 100644 MyBankApp/Podfile.lock diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index f74f998e9..0221d90b1 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 1999FDEFC6310BFE84329E86 /* Pods_MyBankAppTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C337E965EE80D701CC09ED0D /* Pods_MyBankAppTests.framework */; }; + 1D3DCA54D867A351A2E52696 /* Pods_MyBankApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CEB3D9AED855921215D3A98 /* Pods_MyBankApp.framework */; }; + 4F5A4BD5DE23F65DE5F0A06E /* Pods_MyBankApp_MyBankAppUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39BB90D19EE7C0FCD034B3EF /* Pods_MyBankApp_MyBankAppUITests.framework */; }; 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */; }; 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */; }; 5EF1ACB22A4E47C6002EA972 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB12A4E47C6002EA972 /* ViewController.swift */; }; @@ -36,6 +39,12 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 1D934480DE6FAFDC4888F38E /* Pods-MyBankApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyBankApp.debug.xcconfig"; path = "Target Support Files/Pods-MyBankApp/Pods-MyBankApp.debug.xcconfig"; sourceTree = ""; }; + 20FA089B898C9F88264E0F2B /* Pods-MyBankApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyBankApp.release.xcconfig"; path = "Target Support Files/Pods-MyBankApp/Pods-MyBankApp.release.xcconfig"; sourceTree = ""; }; + 364B4B14C6AB11DA11030F62 /* Pods-MyBankAppTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyBankAppTests.debug.xcconfig"; path = "Target Support Files/Pods-MyBankAppTests/Pods-MyBankAppTests.debug.xcconfig"; sourceTree = ""; }; + 39BB90D19EE7C0FCD034B3EF /* Pods_MyBankApp_MyBankAppUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MyBankApp_MyBankAppUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3CEB3D9AED855921215D3A98 /* Pods_MyBankApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MyBankApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4FF8791C0A8C3F3ECC7E00B1 /* Pods-MyBankApp-MyBankAppUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyBankApp-MyBankAppUITests.release.xcconfig"; path = "Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests.release.xcconfig"; sourceTree = ""; }; 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyBankApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -49,6 +58,9 @@ 5EF1ACCA2A4E47C9002EA972 /* MyBankAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyBankAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACCE2A4E47C9002EA972 /* MyBankAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyBankAppUITests.swift; sourceTree = ""; }; 5EF1ACD02A4E47C9002EA972 /* MyBankAppUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyBankAppUITestsLaunchTests.swift; sourceTree = ""; }; + C337E965EE80D701CC09ED0D /* Pods_MyBankAppTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MyBankAppTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D66AB948202BF58D8CE4C347 /* Pods-MyBankApp-MyBankAppUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyBankApp-MyBankAppUITests.debug.xcconfig"; path = "Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests.debug.xcconfig"; sourceTree = ""; }; + E33569CFF49BE73CD4CE261C /* Pods-MyBankAppTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyBankAppTests.release.xcconfig"; path = "Target Support Files/Pods-MyBankAppTests/Pods-MyBankAppTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,6 +68,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1D3DCA54D867A351A2E52696 /* Pods_MyBankApp.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -63,6 +76,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1999FDEFC6310BFE84329E86 /* Pods_MyBankAppTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -70,12 +84,36 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4F5A4BD5DE23F65DE5F0A06E /* Pods_MyBankApp_MyBankAppUITests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0B4CB77341CFCCE73463B88C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3CEB3D9AED855921215D3A98 /* Pods_MyBankApp.framework */, + 39BB90D19EE7C0FCD034B3EF /* Pods_MyBankApp_MyBankAppUITests.framework */, + C337E965EE80D701CC09ED0D /* Pods_MyBankAppTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 3F0964CD2917D53AD2E5358D /* Pods */ = { + isa = PBXGroup; + children = ( + 1D934480DE6FAFDC4888F38E /* Pods-MyBankApp.debug.xcconfig */, + 20FA089B898C9F88264E0F2B /* Pods-MyBankApp.release.xcconfig */, + D66AB948202BF58D8CE4C347 /* Pods-MyBankApp-MyBankAppUITests.debug.xcconfig */, + 4FF8791C0A8C3F3ECC7E00B1 /* Pods-MyBankApp-MyBankAppUITests.release.xcconfig */, + 364B4B14C6AB11DA11030F62 /* Pods-MyBankAppTests.debug.xcconfig */, + E33569CFF49BE73CD4CE261C /* Pods-MyBankAppTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; 5EF1ACA12A4E47C6002EA972 = { isa = PBXGroup; children = ( @@ -83,6 +121,8 @@ 5EF1ACC32A4E47C9002EA972 /* MyBankAppTests */, 5EF1ACCD2A4E47C9002EA972 /* MyBankAppUITests */, 5EF1ACAB2A4E47C6002EA972 /* Products */, + 3F0964CD2917D53AD2E5358D /* Pods */, + 0B4CB77341CFCCE73463B88C /* Frameworks */, ); sourceTree = ""; }; @@ -134,9 +174,11 @@ isa = PBXNativeTarget; buildConfigurationList = 5EF1ACD42A4E47C9002EA972 /* Build configuration list for PBXNativeTarget "MyBankApp" */; buildPhases = ( + B586E5958C28FD1C7110E56C /* [CP] Check Pods Manifest.lock */, 5EF1ACA62A4E47C6002EA972 /* Sources */, 5EF1ACA72A4E47C6002EA972 /* Frameworks */, 5EF1ACA82A4E47C6002EA972 /* Resources */, + D0BC799AEC15A7F58CA95A14 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -151,9 +193,11 @@ isa = PBXNativeTarget; buildConfigurationList = 5EF1ACD72A4E47C9002EA972 /* Build configuration list for PBXNativeTarget "MyBankAppTests" */; buildPhases = ( + BA99DD3DC452AA0EBA74C6B3 /* [CP] Check Pods Manifest.lock */, 5EF1ACBC2A4E47C9002EA972 /* Sources */, 5EF1ACBD2A4E47C9002EA972 /* Frameworks */, 5EF1ACBE2A4E47C9002EA972 /* Resources */, + CCACCCB6ECA48F6DA40BC4EC /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -169,9 +213,11 @@ isa = PBXNativeTarget; buildConfigurationList = 5EF1ACDA2A4E47C9002EA972 /* Build configuration list for PBXNativeTarget "MyBankAppUITests" */; buildPhases = ( + 1142FE0886F0FDC6A8D23367 /* [CP] Check Pods Manifest.lock */, 5EF1ACC62A4E47C9002EA972 /* Sources */, 5EF1ACC72A4E47C9002EA972 /* Frameworks */, 5EF1ACC82A4E47C9002EA972 /* Resources */, + 5283A1C67CEE7CC88BFA9EAF /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -253,6 +299,126 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 1142FE0886F0FDC6A8D23367 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-MyBankApp-MyBankAppUITests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 5283A1C67CEE7CC88BFA9EAF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B586E5958C28FD1C7110E56C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-MyBankApp-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + BA99DD3DC452AA0EBA74C6B3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-MyBankAppTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + CCACCCB6ECA48F6DA40BC4EC /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-MyBankAppTests/Pods-MyBankAppTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-MyBankAppTests/Pods-MyBankAppTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MyBankAppTests/Pods-MyBankAppTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + D0BC799AEC15A7F58CA95A14 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-MyBankApp/Pods-MyBankApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-MyBankApp/Pods-MyBankApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MyBankApp/Pods-MyBankApp-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 5EF1ACA62A4E47C6002EA972 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -366,7 +532,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -420,7 +586,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -432,6 +598,7 @@ }; 5EF1ACD52A4E47C9002EA972 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1D934480DE6FAFDC4888F38E /* Pods-MyBankApp.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -460,6 +627,7 @@ }; 5EF1ACD62A4E47C9002EA972 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 20FA089B898C9F88264E0F2B /* Pods-MyBankApp.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -488,6 +656,7 @@ }; 5EF1ACD82A4E47C9002EA972 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 364B4B14C6AB11DA11030F62 /* Pods-MyBankAppTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -508,6 +677,7 @@ }; 5EF1ACD92A4E47C9002EA972 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = E33569CFF49BE73CD4CE261C /* Pods-MyBankAppTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -528,6 +698,7 @@ }; 5EF1ACDB2A4E47C9002EA972 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D66AB948202BF58D8CE4C347 /* Pods-MyBankApp-MyBankAppUITests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; @@ -546,6 +717,7 @@ }; 5EF1ACDC2A4E47C9002EA972 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 4FF8791C0A8C3F3ECC7E00B1 /* Pods-MyBankApp-MyBankAppUITests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; diff --git a/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist b/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist index d68c618e7..d12158521 100644 --- a/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ MyBankApp.xcscheme_^#shared#^_ orderHint - 0 + 3 diff --git a/MyBankApp/MyBankApp.xcworkspace/contents.xcworkspacedata b/MyBankApp/MyBankApp.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..5cb6c87a6 --- /dev/null +++ b/MyBankApp/MyBankApp.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/MyBankApp/MyBankApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/MyBankApp/MyBankApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/MyBankApp/MyBankApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/MyBankApp/Podfile b/MyBankApp/Podfile new file mode 100644 index 000000000..ededd09d7 --- /dev/null +++ b/MyBankApp/Podfile @@ -0,0 +1,22 @@ +# Uncomment the next line to define a global platform for your project + platform :ios, '16.0' + +target 'MyBankApp' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for MyBankApp + pod 'SnapKit' + pod 'PromiseKit' + + target 'MyBankAppTests' do + inherit! :search_paths + # Pods for testing + pod 'SnapshotTesting' + end + + target 'MyBankAppUITests' do + # Pods for testing + end + +end \ No newline at end of file diff --git a/MyBankApp/Podfile.lock b/MyBankApp/Podfile.lock new file mode 100644 index 000000000..c9185f0c7 --- /dev/null +++ b/MyBankApp/Podfile.lock @@ -0,0 +1,32 @@ +PODS: + - PromiseKit (8.0.0): + - PromiseKit/CorePromise (= 8.0.0) + - PromiseKit/Foundation (= 8.0.0) + - PromiseKit/UIKit (= 8.0.0) + - PromiseKit/CorePromise (8.0.0) + - PromiseKit/Foundation (8.0.0): + - PromiseKit/CorePromise + - PromiseKit/UIKit (8.0.0): + - PromiseKit/CorePromise + - SnapKit (5.6.0) + - SnapshotTesting (1.9.0) + +DEPENDENCIES: + - PromiseKit + - SnapKit + - SnapshotTesting + +SPEC REPOS: + trunk: + - PromiseKit + - SnapKit + - SnapshotTesting + +SPEC CHECKSUMS: + PromiseKit: 7039707ba8764b6ce98501debce2489561b038a5 + SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25 + SnapshotTesting: 6141c48b6aa76ead61431ca665c14ab9a066c53b + +PODFILE CHECKSUM: 385464d08169c5d2af06404b0b805446588bbf79 + +COCOAPODS: 1.12.1 From a46c3b57be4a51d2bd568f901d1834d7a9f73f80 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Thu, 29 Jun 2023 22:30:06 -0300 Subject: [PATCH 04/27] ajustando ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES para nao aparecer mais no pod install --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 4 ---- .../xcschemes/xcschememanagement.plist | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index 0221d90b1..ae55376c2 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -658,7 +658,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 364B4B14C6AB11DA11030F62 /* Pods-MyBankAppTests.debug.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -679,7 +678,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = E33569CFF49BE73CD4CE261C /* Pods-MyBankAppTests.release.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -700,7 +698,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = D66AB948202BF58D8CE4C347 /* Pods-MyBankApp-MyBankAppUITests.debug.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = D67XBEEN3L; @@ -719,7 +716,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 4FF8791C0A8C3F3ECC7E00B1 /* Pods-MyBankApp-MyBankAppUITests.release.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = D67XBEEN3L; diff --git a/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist b/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist index d12158521..8aa4d188b 100644 --- a/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ MyBankApp.xcscheme_^#shared#^_ orderHint - 3 + 6 From a73c7604b80eb18f16b6e00349b23054e47086d3 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Sat, 1 Jul 2023 16:06:18 -0300 Subject: [PATCH 05/27] =?UTF-8?q?Atualizando=20vers=C3=A3o=20m=C3=ADnima?= =?UTF-8?q?=20do=20iOS=20no=20projeto=20e=20podfile=20pra=2011=20ou=20supe?= =?UTF-8?q?rior.=20Criando=20classe=20base=20de=20Network,=20e=20j=C3=A1?= =?UTF-8?q?=20deixando=20pronto=20Login=20e=20Home=20com=20seus=20respecti?= =?UTF-8?q?vos=20ResponseModels.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 74 +++++++++++++++++++ MyBankApp/MyBankApp/Home/HomeService.swift | 53 +++++++++++++ .../MyBankApp/Home/Models/HomeResponse.swift | 13 ++++ MyBankApp/MyBankApp/Login/LoginService.swift | 49 ++++++++++++ .../Login/Models/LoginResponse.swift | 5 ++ MyBankApp/MyBankApp/Network/HTTPMethod.swift | 6 ++ .../MyBankApp/Network/NetworkError.swift | 18 +++++ .../MyBankApp/Network/NetworkProtocols.swift | 12 +++ .../Network/NetworkServiceImpl.swift | 54 ++++++++++++++ MyBankApp/Podfile | 4 +- 10 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 MyBankApp/MyBankApp/Home/HomeService.swift create mode 100644 MyBankApp/MyBankApp/Home/Models/HomeResponse.swift create mode 100644 MyBankApp/MyBankApp/Login/LoginService.swift create mode 100644 MyBankApp/MyBankApp/Login/Models/LoginResponse.swift create mode 100644 MyBankApp/MyBankApp/Network/HTTPMethod.swift create mode 100644 MyBankApp/MyBankApp/Network/NetworkError.swift create mode 100644 MyBankApp/MyBankApp/Network/NetworkProtocols.swift create mode 100644 MyBankApp/MyBankApp/Network/NetworkServiceImpl.swift diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index ae55376c2..035bdf098 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -10,6 +10,14 @@ 1999FDEFC6310BFE84329E86 /* Pods_MyBankAppTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C337E965EE80D701CC09ED0D /* Pods_MyBankAppTests.framework */; }; 1D3DCA54D867A351A2E52696 /* Pods_MyBankApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CEB3D9AED855921215D3A98 /* Pods_MyBankApp.framework */; }; 4F5A4BD5DE23F65DE5F0A06E /* Pods_MyBankApp_MyBankAppUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39BB90D19EE7C0FCD034B3EF /* Pods_MyBankApp_MyBankAppUITests.framework */; }; + 5EEFAB4E2A50982B00A000EE /* NetworkProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB4D2A50982B00A000EE /* NetworkProtocols.swift */; }; + 5EEFAB572A50A81900A000EE /* LoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB562A50A81900A000EE /* LoginService.swift */; }; + 5EEFAB592A50A84300A000EE /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB582A50A84300A000EE /* NetworkError.swift */; }; + 5EEFAB5B2A50A85B00A000EE /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB5A2A50A85B00A000EE /* HTTPMethod.swift */; }; + 5EEFAB5D2A50A88600A000EE /* NetworkServiceImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB5C2A50A88600A000EE /* NetworkServiceImpl.swift */; }; + 5EEFAB622A50A8E500A000EE /* HomeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB612A50A8E500A000EE /* HomeResponse.swift */; }; + 5EEFAB642A50A8F900A000EE /* HomeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB632A50A8F900A000EE /* HomeService.swift */; }; + 5EEFAB672A50A99C00A000EE /* LoginResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB662A50A99C00A000EE /* LoginResponse.swift */; }; 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */; }; 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */; }; 5EF1ACB22A4E47C6002EA972 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB12A4E47C6002EA972 /* ViewController.swift */; }; @@ -45,6 +53,14 @@ 39BB90D19EE7C0FCD034B3EF /* Pods_MyBankApp_MyBankAppUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MyBankApp_MyBankAppUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3CEB3D9AED855921215D3A98 /* Pods_MyBankApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MyBankApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4FF8791C0A8C3F3ECC7E00B1 /* Pods-MyBankApp-MyBankAppUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyBankApp-MyBankAppUITests.release.xcconfig"; path = "Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests.release.xcconfig"; sourceTree = ""; }; + 5EEFAB4D2A50982B00A000EE /* NetworkProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtocols.swift; sourceTree = ""; }; + 5EEFAB562A50A81900A000EE /* LoginService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginService.swift; sourceTree = ""; }; + 5EEFAB582A50A84300A000EE /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; + 5EEFAB5A2A50A85B00A000EE /* HTTPMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; + 5EEFAB5C2A50A88600A000EE /* NetworkServiceImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkServiceImpl.swift; sourceTree = ""; }; + 5EEFAB612A50A8E500A000EE /* HomeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeResponse.swift; sourceTree = ""; }; + 5EEFAB632A50A8F900A000EE /* HomeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeService.swift; sourceTree = ""; }; + 5EEFAB662A50A99C00A000EE /* LoginResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginResponse.swift; sourceTree = ""; }; 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyBankApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -114,6 +130,51 @@ path = Pods; sourceTree = ""; }; + 5EEFAB442A50739500A000EE /* Network */ = { + isa = PBXGroup; + children = ( + 5EEFAB4D2A50982B00A000EE /* NetworkProtocols.swift */, + 5EEFAB582A50A84300A000EE /* NetworkError.swift */, + 5EEFAB5A2A50A85B00A000EE /* HTTPMethod.swift */, + 5EEFAB5C2A50A88600A000EE /* NetworkServiceImpl.swift */, + ); + path = Network; + sourceTree = ""; + }; + 5EEFAB5E2A50A8BE00A000EE /* Login */ = { + isa = PBXGroup; + children = ( + 5EEFAB652A50A99300A000EE /* Models */, + 5EEFAB562A50A81900A000EE /* LoginService.swift */, + ); + path = Login; + sourceTree = ""; + }; + 5EEFAB5F2A50A8CB00A000EE /* Home */ = { + isa = PBXGroup; + children = ( + 5EEFAB602A50A8DE00A000EE /* Models */, + 5EEFAB632A50A8F900A000EE /* HomeService.swift */, + ); + path = Home; + sourceTree = ""; + }; + 5EEFAB602A50A8DE00A000EE /* Models */ = { + isa = PBXGroup; + children = ( + 5EEFAB612A50A8E500A000EE /* HomeResponse.swift */, + ); + path = Models; + sourceTree = ""; + }; + 5EEFAB652A50A99300A000EE /* Models */ = { + isa = PBXGroup; + children = ( + 5EEFAB662A50A99C00A000EE /* LoginResponse.swift */, + ); + path = Models; + sourceTree = ""; + }; 5EF1ACA12A4E47C6002EA972 = { isa = PBXGroup; children = ( @@ -139,6 +200,9 @@ 5EF1ACAC2A4E47C6002EA972 /* MyBankApp */ = { isa = PBXGroup; children = ( + 5EEFAB5F2A50A8CB00A000EE /* Home */, + 5EEFAB5E2A50A8BE00A000EE /* Login */, + 5EEFAB442A50739500A000EE /* Network */, 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */, 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */, 5EF1ACB12A4E47C6002EA972 /* ViewController.swift */, @@ -424,8 +488,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5EEFAB622A50A8E500A000EE /* HomeResponse.swift in Sources */, + 5EEFAB5D2A50A88600A000EE /* NetworkServiceImpl.swift in Sources */, + 5EEFAB4E2A50982B00A000EE /* NetworkProtocols.swift in Sources */, 5EF1ACB22A4E47C6002EA972 /* ViewController.swift in Sources */, + 5EEFAB642A50A8F900A000EE /* HomeService.swift in Sources */, + 5EEFAB5B2A50A85B00A000EE /* HTTPMethod.swift in Sources */, + 5EEFAB572A50A81900A000EE /* LoginService.swift in Sources */, + 5EEFAB592A50A84300A000EE /* NetworkError.swift in Sources */, 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */, + 5EEFAB672A50A99C00A000EE /* LoginResponse.swift in Sources */, 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -612,6 +684,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -641,6 +714,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/MyBankApp/MyBankApp/Home/HomeService.swift b/MyBankApp/MyBankApp/Home/HomeService.swift new file mode 100644 index 000000000..bb86fc8d5 --- /dev/null +++ b/MyBankApp/MyBankApp/Home/HomeService.swift @@ -0,0 +1,53 @@ +import Foundation + +protocol HomeServiceProtocol { + func fetchStatements(for id: String, completion: @escaping (Result) -> Void) +} + +final class HomeService: HomeServiceProtocol { + private let networkService: NetworkService + + init(networkService: NetworkService = NetworkServiceImpl()) { + self.networkService = networkService + } + + func fetchStatements( + for id: String, + completion: @escaping (Result) -> Void + ) { + guard let url = URL(string: "https://your-api-url.com/home/\(id)") else { + completion(.failure(.invalidResponse)) + return + } + + networkService.request( + url: url, + method: .get, + bodyParameters: nil + ) { (result: Result) in + completion(result) + } + } +} + +/* + let homeService = HomeService() + + homeService.fetchStatements(for: "your-id") { result in + switch result { + case .success(let statements): + // Handle successful fetch + for statement in statements { + print("Statement ID:", statement.id) + print("Type:", statement.type) + print("Date:", statement.date) + print("Detail:", statement.detail) + print("Value:", statement.value) + print("--------") + } + case .failure(let error): + // Handle fetch error + print("Fetch error:", error.localizedDescription) + } + } + */ diff --git a/MyBankApp/MyBankApp/Home/Models/HomeResponse.swift b/MyBankApp/MyBankApp/Home/Models/HomeResponse.swift new file mode 100644 index 000000000..e4a953cc4 --- /dev/null +++ b/MyBankApp/MyBankApp/Home/Models/HomeResponse.swift @@ -0,0 +1,13 @@ +import Foundation + +struct HomeResponse: Decodable { + let statement: [Statement] +} + +struct Statement: Decodable { + let id: Int + let type: String + let date: String + let detail: String + let value: Double +} diff --git a/MyBankApp/MyBankApp/Login/LoginService.swift b/MyBankApp/MyBankApp/Login/LoginService.swift new file mode 100644 index 000000000..b0230d15d --- /dev/null +++ b/MyBankApp/MyBankApp/Login/LoginService.swift @@ -0,0 +1,49 @@ +import Foundation + +protocol LoginServiceProtocol { + func login(user: String, password: String, completion: @escaping (Result) -> Void) +} + +final class LoginService: LoginServiceProtocol { + private let networkService: NetworkService + + init(networkService: NetworkService = NetworkServiceImpl()) { + self.networkService = networkService + } + + func login( + user: String, + password: String, + completion: @escaping (Result) -> Void + ) { + guard let url = URL(string: "https://your-login-api-url.com/login") else { + completion(.failure(.invalidResponse)) + return + } + + let bodyParameters = ["user": user, "password": password] + + networkService.request( + url: url, + method: .post, + bodyParameters: bodyParameters + ) { (result: Result) in + completion(result) + } + } +} + +/* + let loginService = LoginService() + + loginService.login(user: "your-username", password: "your-password") { result in + switch result { + case .success(let response): + // Handle successful login + print("Logged in with ID:", response.id) + case .failure(let error): + // Handle login error + print("Login error:", error.localizedDescription) + } + } + */ diff --git a/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift b/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift new file mode 100644 index 000000000..f7c4901b7 --- /dev/null +++ b/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift @@ -0,0 +1,5 @@ +import Foundation + +struct LoginResponse: Decodable { + let id: String +} diff --git a/MyBankApp/MyBankApp/Network/HTTPMethod.swift b/MyBankApp/MyBankApp/Network/HTTPMethod.swift new file mode 100644 index 000000000..e45d8bf46 --- /dev/null +++ b/MyBankApp/MyBankApp/Network/HTTPMethod.swift @@ -0,0 +1,6 @@ +import Foundation + +enum HTTPMethod: String { + case get = "GET" + case post = "POST" +} diff --git a/MyBankApp/MyBankApp/Network/NetworkError.swift b/MyBankApp/MyBankApp/Network/NetworkError.swift new file mode 100644 index 000000000..9acfaf744 --- /dev/null +++ b/MyBankApp/MyBankApp/Network/NetworkError.swift @@ -0,0 +1,18 @@ +import Foundation + +enum NetworkError: Error { + case requestFailed + case invalidResponse + case decodingFailed + + var localizedDescription: String { + switch self { + case .requestFailed: + return "Request failed" + case .invalidResponse: + return "Invalid response received" + case .decodingFailed: + return "Decoding failed" + } + } +} diff --git a/MyBankApp/MyBankApp/Network/NetworkProtocols.swift b/MyBankApp/MyBankApp/Network/NetworkProtocols.swift new file mode 100644 index 000000000..400b89257 --- /dev/null +++ b/MyBankApp/MyBankApp/Network/NetworkProtocols.swift @@ -0,0 +1,12 @@ +import Foundation + +protocol NetworkService { + func request(url: URL, method: HTTPMethod, bodyParameters: [String: Any]?, + completion: @escaping (Result) -> Void) +} + +protocol URLSessionProtocol { + func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask +} + +extension URLSession: URLSessionProtocol {} diff --git a/MyBankApp/MyBankApp/Network/NetworkServiceImpl.swift b/MyBankApp/MyBankApp/Network/NetworkServiceImpl.swift new file mode 100644 index 000000000..791f2a45f --- /dev/null +++ b/MyBankApp/MyBankApp/Network/NetworkServiceImpl.swift @@ -0,0 +1,54 @@ +import Foundation + +final class NetworkServiceImpl: NetworkService { + private let urlSession: URLSessionProtocol + + init(urlSession: URLSessionProtocol = URLSession.shared) { + self.urlSession = urlSession + } + + func request( + url: URL, + method: HTTPMethod, + bodyParameters: [String: Any]?, + completion: @escaping (Result) -> Void + ) { + var request = URLRequest(url: url) + request.httpMethod = method.rawValue + + // Set body parameters if available + if let bodyParameters = bodyParameters { + request.httpBody = try? JSONSerialization.data(withJSONObject: bodyParameters) + } + + let task = urlSession.dataTask(with: request) { data, response, error in + // Check for network errors + if let error = error { + completion(.failure(NetworkError.requestFailed)) + return + } + + // Check for invalid response + guard let httpResponse = response as? HTTPURLResponse, + (200...299).contains(httpResponse.statusCode) else { + completion(.failure(NetworkError.invalidResponse)) + return + } + + // Parse the response data + guard let responseData = data else { + completion(.failure(NetworkError.decodingFailed)) + return + } + + do { + let decodedObject = try JSONDecoder().decode(T.self, from: responseData) + completion(.success(decodedObject)) + } catch { + completion(.failure(NetworkError.decodingFailed)) + } + } + + task.resume() + } +} diff --git a/MyBankApp/Podfile b/MyBankApp/Podfile index ededd09d7..b2373787f 100644 --- a/MyBankApp/Podfile +++ b/MyBankApp/Podfile @@ -1,5 +1,5 @@ # Uncomment the next line to define a global platform for your project - platform :ios, '16.0' + platform :ios, '11.0' target 'MyBankApp' do # Comment the next line if you don't want to use dynamic frameworks @@ -19,4 +19,4 @@ target 'MyBankApp' do # Pods for testing end -end \ No newline at end of file +end From 9e98fe006ebff591961b0d7bc14880d0456b72f8 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Sat, 1 Jul 2023 17:43:55 -0300 Subject: [PATCH 06/27] Ajustando NetworkServiceSpy. Criando LoginServiceTests e HomeServiceTests. Atualizando URL dos testes. Atualizando AppDelegate e SceneDelegate. --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 40 ++++++- .../xcshareddata/xcschemes/MyBankApp.xcscheme | 101 ++++++++++++++++++ .../xcschemes/xcschememanagement.plist | 18 ++++ MyBankApp/MyBankApp/AppDelegate.swift | 2 + MyBankApp/MyBankApp/Home/HomeService.swift | 2 +- .../MyBankApp/Home/Models/HomeResponse.swift | 2 +- MyBankApp/MyBankApp/Login/LoginService.swift | 2 +- .../MyBankApp/Network/NetworkError.swift | 6 ++ MyBankApp/MyBankApp/SceneDelegate.swift | 6 ++ .../Home/HomeServiceTests.swift | 81 ++++++++++++++ .../Login/LoginServiceTests.swift | 71 ++++++++++++ MyBankApp/MyBankAppTests/MyBankAppTests.swift | 36 ------- .../Network/NetworkServiceSpy.swift | 35 ++++++ 13 files changed, 359 insertions(+), 43 deletions(-) create mode 100644 MyBankApp/MyBankApp.xcodeproj/xcshareddata/xcschemes/MyBankApp.xcscheme create mode 100644 MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift create mode 100644 MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift delete mode 100644 MyBankApp/MyBankAppTests/MyBankAppTests.swift create mode 100644 MyBankApp/MyBankAppTests/Network/NetworkServiceSpy.swift diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index 035bdf098..5ff9e799b 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -18,13 +18,15 @@ 5EEFAB622A50A8E500A000EE /* HomeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB612A50A8E500A000EE /* HomeResponse.swift */; }; 5EEFAB642A50A8F900A000EE /* HomeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB632A50A8F900A000EE /* HomeService.swift */; }; 5EEFAB672A50A99C00A000EE /* LoginResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB662A50A99C00A000EE /* LoginResponse.swift */; }; + 5EEFAB6C2A50B16E00A000EE /* NetworkServiceSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB6B2A50B16E00A000EE /* NetworkServiceSpy.swift */; }; + 5EEFAB6F2A50B21700A000EE /* LoginServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB6E2A50B21700A000EE /* LoginServiceTests.swift */; }; + 5EEFAB722A50B80200A000EE /* HomeServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB712A50B80200A000EE /* HomeServiceTests.swift */; }; 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */; }; 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */; }; 5EF1ACB22A4E47C6002EA972 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB12A4E47C6002EA972 /* ViewController.swift */; }; 5EF1ACB52A4E47C6002EA972 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB32A4E47C6002EA972 /* Main.storyboard */; }; 5EF1ACB72A4E47C9002EA972 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB62A4E47C9002EA972 /* Assets.xcassets */; }; 5EF1ACBA2A4E47C9002EA972 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB82A4E47C9002EA972 /* LaunchScreen.storyboard */; }; - 5EF1ACC52A4E47C9002EA972 /* MyBankAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACC42A4E47C9002EA972 /* MyBankAppTests.swift */; }; 5EF1ACCF2A4E47C9002EA972 /* MyBankAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACCE2A4E47C9002EA972 /* MyBankAppUITests.swift */; }; 5EF1ACD12A4E47C9002EA972 /* MyBankAppUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACD02A4E47C9002EA972 /* MyBankAppUITestsLaunchTests.swift */; }; /* End PBXBuildFile section */ @@ -61,6 +63,9 @@ 5EEFAB612A50A8E500A000EE /* HomeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeResponse.swift; sourceTree = ""; }; 5EEFAB632A50A8F900A000EE /* HomeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeService.swift; sourceTree = ""; }; 5EEFAB662A50A99C00A000EE /* LoginResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginResponse.swift; sourceTree = ""; }; + 5EEFAB6B2A50B16E00A000EE /* NetworkServiceSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkServiceSpy.swift; sourceTree = ""; }; + 5EEFAB6E2A50B21700A000EE /* LoginServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginServiceTests.swift; sourceTree = ""; }; + 5EEFAB712A50B80200A000EE /* HomeServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeServiceTests.swift; sourceTree = ""; }; 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyBankApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -70,7 +75,6 @@ 5EF1ACB92A4E47C9002EA972 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 5EF1ACBB2A4E47C9002EA972 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5EF1ACC02A4E47C9002EA972 /* MyBankAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyBankAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 5EF1ACC42A4E47C9002EA972 /* MyBankAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyBankAppTests.swift; sourceTree = ""; }; 5EF1ACCA2A4E47C9002EA972 /* MyBankAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyBankAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACCE2A4E47C9002EA972 /* MyBankAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyBankAppUITests.swift; sourceTree = ""; }; 5EF1ACD02A4E47C9002EA972 /* MyBankAppUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyBankAppUITestsLaunchTests.swift; sourceTree = ""; }; @@ -175,6 +179,30 @@ path = Models; sourceTree = ""; }; + 5EEFAB6A2A50B15700A000EE /* Network */ = { + isa = PBXGroup; + children = ( + 5EEFAB6B2A50B16E00A000EE /* NetworkServiceSpy.swift */, + ); + path = Network; + sourceTree = ""; + }; + 5EEFAB6D2A50B20A00A000EE /* Login */ = { + isa = PBXGroup; + children = ( + 5EEFAB6E2A50B21700A000EE /* LoginServiceTests.swift */, + ); + path = Login; + sourceTree = ""; + }; + 5EEFAB702A50B7F900A000EE /* Home */ = { + isa = PBXGroup; + children = ( + 5EEFAB712A50B80200A000EE /* HomeServiceTests.swift */, + ); + path = Home; + sourceTree = ""; + }; 5EF1ACA12A4E47C6002EA972 = { isa = PBXGroup; children = ( @@ -217,7 +245,9 @@ 5EF1ACC32A4E47C9002EA972 /* MyBankAppTests */ = { isa = PBXGroup; children = ( - 5EF1ACC42A4E47C9002EA972 /* MyBankAppTests.swift */, + 5EEFAB702A50B7F900A000EE /* Home */, + 5EEFAB6D2A50B20A00A000EE /* Login */, + 5EEFAB6A2A50B15700A000EE /* Network */, ); path = MyBankAppTests; sourceTree = ""; @@ -506,7 +536,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5EF1ACC52A4E47C9002EA972 /* MyBankAppTests.swift in Sources */, + 5EEFAB6F2A50B21700A000EE /* LoginServiceTests.swift in Sources */, + 5EEFAB722A50B80200A000EE /* HomeServiceTests.swift in Sources */, + 5EEFAB6C2A50B16E00A000EE /* NetworkServiceSpy.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MyBankApp/MyBankApp.xcodeproj/xcshareddata/xcschemes/MyBankApp.xcscheme b/MyBankApp/MyBankApp.xcodeproj/xcshareddata/xcschemes/MyBankApp.xcscheme new file mode 100644 index 000000000..5cd5c6b51 --- /dev/null +++ b/MyBankApp/MyBankApp.xcodeproj/xcshareddata/xcschemes/MyBankApp.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist b/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist index 8aa4d188b..4d76879df 100644 --- a/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/MyBankApp/MyBankApp.xcodeproj/xcuserdata/matheusfusco.xcuserdatad/xcschemes/xcschememanagement.plist @@ -10,5 +10,23 @@ 6 + SuppressBuildableAutocreation + + 5EF1ACA92A4E47C6002EA972 + + primary + + + 5EF1ACBF2A4E47C9002EA972 + + primary + + + 5EF1ACC92A4E47C9002EA972 + + primary + + + diff --git a/MyBankApp/MyBankApp/AppDelegate.swift b/MyBankApp/MyBankApp/AppDelegate.swift index 50dcfa86e..3e5c2130e 100644 --- a/MyBankApp/MyBankApp/AppDelegate.swift +++ b/MyBankApp/MyBankApp/AppDelegate.swift @@ -19,12 +19,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: UISceneSession Lifecycle + @available(iOS 13.0, *) func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } + @available(iOS 13.0, *) func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. diff --git a/MyBankApp/MyBankApp/Home/HomeService.swift b/MyBankApp/MyBankApp/Home/HomeService.swift index bb86fc8d5..ef9677974 100644 --- a/MyBankApp/MyBankApp/Home/HomeService.swift +++ b/MyBankApp/MyBankApp/Home/HomeService.swift @@ -15,7 +15,7 @@ final class HomeService: HomeServiceProtocol { for id: String, completion: @escaping (Result) -> Void ) { - guard let url = URL(string: "https://your-api-url.com/home/\(id)") else { + guard let url = URL(string: "https://6092aef785ff5100172136c2.mockapi.io/api/statements/\(id)") else { completion(.failure(.invalidResponse)) return } diff --git a/MyBankApp/MyBankApp/Home/Models/HomeResponse.swift b/MyBankApp/MyBankApp/Home/Models/HomeResponse.swift index e4a953cc4..9a4712152 100644 --- a/MyBankApp/MyBankApp/Home/Models/HomeResponse.swift +++ b/MyBankApp/MyBankApp/Home/Models/HomeResponse.swift @@ -4,7 +4,7 @@ struct HomeResponse: Decodable { let statement: [Statement] } -struct Statement: Decodable { +struct Statement: Decodable, Equatable { let id: Int let type: String let date: String diff --git a/MyBankApp/MyBankApp/Login/LoginService.swift b/MyBankApp/MyBankApp/Login/LoginService.swift index b0230d15d..9836453c5 100644 --- a/MyBankApp/MyBankApp/Login/LoginService.swift +++ b/MyBankApp/MyBankApp/Login/LoginService.swift @@ -16,7 +16,7 @@ final class LoginService: LoginServiceProtocol { password: String, completion: @escaping (Result) -> Void ) { - guard let url = URL(string: "https://your-login-api-url.com/login") else { + guard let url = URL(string: "https://6092aef785ff5100172136c2.mockapi.io/api/login") else { completion(.failure(.invalidResponse)) return } diff --git a/MyBankApp/MyBankApp/Network/NetworkError.swift b/MyBankApp/MyBankApp/Network/NetworkError.swift index 9acfaf744..d4632277b 100644 --- a/MyBankApp/MyBankApp/Network/NetworkError.swift +++ b/MyBankApp/MyBankApp/Network/NetworkError.swift @@ -4,6 +4,8 @@ enum NetworkError: Error { case requestFailed case invalidResponse case decodingFailed + case noData + case unknownError var localizedDescription: String { switch self { @@ -13,6 +15,10 @@ enum NetworkError: Error { return "Invalid response received" case .decodingFailed: return "Decoding failed" + case .noData: + return "No Data" + case .unknownError: + return "Unknown Error" } } } diff --git a/MyBankApp/MyBankApp/SceneDelegate.swift b/MyBankApp/MyBankApp/SceneDelegate.swift index 992115f9d..f4424c425 100644 --- a/MyBankApp/MyBankApp/SceneDelegate.swift +++ b/MyBankApp/MyBankApp/SceneDelegate.swift @@ -12,6 +12,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? + @available(iOS 13.0, *) func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. @@ -19,6 +20,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let _ = (scene as? UIWindowScene) else { return } } + @available(iOS 13.0, *) func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. @@ -26,21 +28,25 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). } + @available(iOS 13.0, *) func sceneDidBecomeActive(_ scene: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } + @available(iOS 13.0, *) func sceneWillResignActive(_ scene: UIScene) { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } + @available(iOS 13.0, *) func sceneWillEnterForeground(_ scene: UIScene) { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } + @available(iOS 13.0, *) func sceneDidEnterBackground(_ scene: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information diff --git a/MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift b/MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift new file mode 100644 index 000000000..fbcab8faa --- /dev/null +++ b/MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift @@ -0,0 +1,81 @@ +@testable import MyBankApp +import XCTest + +final class HomeServiceTests: XCTestCase { + private var sut: HomeService! + private var networkServiceSpy: NetworkServiceSpy! + + override func setUp() { + super.setUp() + networkServiceSpy = NetworkServiceSpy() + sut = HomeService(networkService: networkServiceSpy) + } + + override func tearDown() { + sut = nil + networkServiceSpy = nil + super.tearDown() + } + + func testFetchStatementsSuccess() { + // Given + let expectedStatements = [ + Statement(id: 1, type: "Expense", date: "2023-06-30", detail: "Groceries", value: 50.0), + Statement(id: 2, type: "Income", date: "2023-06-29", detail: "Salary", value: 2000.0) + ] + + let responseJSON = """ + { + "statement": [ + {"id": 1, "type": "Expense", "date": "2023-06-30", "detail": "Groceries", "value": 50.0}, + {"id": 2, "type": "Income", "date": "2023-06-29", "detail": "Salary", "value": 2000.0} + ] + } + """ + + let responseData = responseJSON.data(using: .utf8)! + networkServiceSpy.mockedResult = .success(responseData) + + // When + var capturedResult: Result? + sut.fetchStatements(for: "user123") { result in + capturedResult = result + } + + // Then + XCTAssertEqual(networkServiceSpy.capturedURL?.absoluteString, "https://6092aef785ff5100172136c2.mockapi.io/api/statements/user123") + XCTAssertEqual(networkServiceSpy.capturedMethod, .get) + switch capturedResult { + case .success(let statements): + XCTAssertEqual(statements.statement, expectedStatements) + case .failure(let error): + XCTFail("Expected success, but received failure with error: \(error)") + case .none: + XCTFail("Result is nil") + } + } + + func testFetchStatementsFailure() { + // Given + let expectedError = NetworkError.requestFailed + networkServiceSpy.mockedResult = .failure(expectedError) + + // When + var capturedResult: Result? + sut.fetchStatements(for: "user123") { result in + capturedResult = result + } + + // Then + XCTAssertEqual(networkServiceSpy.capturedURL?.absoluteString, "https://6092aef785ff5100172136c2.mockapi.io/api/statements/user123") + XCTAssertEqual(networkServiceSpy.capturedMethod, .get) + switch capturedResult { + case .success(let statements): + XCTFail("Expected failure, but received success with statements: \(statements)") + case .failure(let error): + XCTAssertEqual(error, expectedError) + case .none: + XCTFail("Result is nil") + } + } +} diff --git a/MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift b/MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift new file mode 100644 index 000000000..a67d93c00 --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift @@ -0,0 +1,71 @@ +@testable import MyBankApp +import XCTest + +final class LoginServiceTests: XCTestCase { + private var sut: LoginService! + private var networkServiceSpy: NetworkServiceSpy! + + override func setUp() { + super.setUp() + networkServiceSpy = NetworkServiceSpy() + sut = LoginService(networkService: networkServiceSpy) + } + + override func tearDown() { + sut = nil + networkServiceSpy = nil + super.tearDown() + } + + func testLoginSuccess() { + // Given + let expectedID = "user123" + let responseJSON = """ + {"id": "\(expectedID)"} + """ + let responseData = responseJSON.data(using: .utf8)! + networkServiceSpy.mockedResult = .success(responseData) + + // When + var capturedResult: Result? + sut.login(user: "testuser", password: "testpassword") { result in + capturedResult = result + } + + // Then + XCTAssertEqual(networkServiceSpy.capturedURL?.absoluteString, "https://6092aef785ff5100172136c2.mockapi.io/api/login") + XCTAssertEqual(networkServiceSpy.capturedMethod, .post) + switch capturedResult { + case let .success(response): + XCTAssertEqual(response.id, expectedID) + case .failure(_): + XCTFail() + case .none: + XCTFail() + } + } + + func testLoginFailure() { + // Given + let expectedError = NetworkError.requestFailed + networkServiceSpy.mockedResult = .failure(expectedError) + + // When + var capturedResult: Result? + sut.login(user: "testuser", password: "testpassword") { result in + capturedResult = result + } + + // Then + XCTAssertEqual(networkServiceSpy.capturedURL?.absoluteString, "https://6092aef785ff5100172136c2.mockapi.io/api/login") + XCTAssertEqual(networkServiceSpy.capturedMethod, .post) + switch capturedResult { + case .success(_): + XCTFail() + case let .failure(error): + XCTAssertEqual(error, expectedError) + case .none: + XCTFail() + } + } +} diff --git a/MyBankApp/MyBankAppTests/MyBankAppTests.swift b/MyBankApp/MyBankAppTests/MyBankAppTests.swift deleted file mode 100644 index c5c13c9f4..000000000 --- a/MyBankApp/MyBankAppTests/MyBankAppTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// MyBankAppTests.swift -// MyBankAppTests -// -// Created by Matheus Fusco on 29/06/23. -// - -import XCTest -@testable import MyBankApp - -final class MyBankAppTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/MyBankApp/MyBankAppTests/Network/NetworkServiceSpy.swift b/MyBankApp/MyBankAppTests/Network/NetworkServiceSpy.swift new file mode 100644 index 000000000..31419360a --- /dev/null +++ b/MyBankApp/MyBankAppTests/Network/NetworkServiceSpy.swift @@ -0,0 +1,35 @@ +@testable import MyBankApp +import XCTest + +typealias NetworkServiceDummy = NetworkServiceSpy + +final class NetworkServiceSpy: NetworkService { + var capturedURL: URL? + var capturedMethod: HTTPMethod? + var capturedBodyParameters: [String: Any]? + var mockedResult: Result? + + func request(url: URL, method: HTTPMethod, bodyParameters: [String: Any]?, completion: @escaping (Result) -> Void) where T: Decodable { + capturedURL = url + capturedMethod = method + capturedBodyParameters = bodyParameters + + switch mockedResult { + case .success(let data): + if let data = data { + do { + let decodedObject = try JSONDecoder().decode(T.self, from: data) + completion(.success(decodedObject)) + } catch { + completion(.failure(.decodingFailed)) + } + } else { + completion(.failure(.noData)) + } + case .failure(_): + completion(.failure(.requestFailed)) + case .none: + completion(.failure(.unknownError)) + } + } +} From c584596ef65603e1918e640c640da43e3f021303 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Sat, 1 Jul 2023 17:51:11 -0300 Subject: [PATCH 07/27] Ajustando NetworkProtocols para tirar o alerta referente a: Sendability of function types in instance method 'dataTask(with:completionHandler:)' does not match requirement in protocol 'URLSessionProtocol' --- MyBankApp/MyBankApp/Network/NetworkProtocols.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/MyBankApp/MyBankApp/Network/NetworkProtocols.swift b/MyBankApp/MyBankApp/Network/NetworkProtocols.swift index 400b89257..17d3d29a6 100644 --- a/MyBankApp/MyBankApp/Network/NetworkProtocols.swift +++ b/MyBankApp/MyBankApp/Network/NetworkProtocols.swift @@ -6,7 +6,17 @@ protocol NetworkService { } protocol URLSessionProtocol { - func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask + func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTaskProtocol } -extension URLSession: URLSessionProtocol {} +protocol URLSessionDataTaskProtocol { + func resume() +} + +extension URLSession: URLSessionProtocol { + func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTaskProtocol { + return dataTask(with: request, completionHandler: completionHandler) as URLSessionDataTask + } +} + +extension URLSessionDataTask: URLSessionDataTaskProtocol {} From 25de7aee26e2a5a18f5d71b05770158a732e0337 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Sat, 1 Jul 2023 19:44:06 -0300 Subject: [PATCH 08/27] =?UTF-8?q?Adicionando=20primeira=20vers=C3=A3o=20da?= =?UTF-8?q?=20LoginViewController.=20Falta=20ajustar=20a=20navega=C3=A7?= =?UTF-8?q?=C3=A3o=20para=20a=20pr=C3=B3xima=20tela=20e=20o=20dado=20a=20s?= =?UTF-8?q?er=20passado=20para=20a=20HomeViewController.=20Proximos=20pass?= =?UTF-8?q?os:=20Criar=20os=20testes=20da=20LoginViewController.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 42 ++++++- MyBankApp/MyBankApp/AppDelegate.swift | 12 +- ...DEBC6CD-73C4-47CB-B879-8A426D8163E1@1x.png | Bin 0 -> 4442 bytes ...DEBC6CD-73C4-47CB-B879-8A426D8163E1@2x.png | Bin 0 -> 9380 bytes ...DEBC6CD-73C4-47CB-B879-8A426D8163E1@3x.png | Bin 0 -> 15523 bytes .../bankLogo.imageset/Contents.json | 23 ++++ .../Login/Scene/LoginConfigurator.swift | 16 +++ .../Login/Scene/LoginInteractor.swift | 25 ++++ .../Login/Scene/LoginPresenter.swift | 13 ++ .../Login/Scene/LoginProtocols.swift | 28 +++++ .../MyBankApp/Login/Scene/LoginRouter.swift | 13 ++ .../Login/Scene/LoginViewController.swift | 112 ++++++++++++++++++ .../Login/{ => Service}/LoginService.swift | 15 --- .../Network/NetworkServiceImpl.swift | 2 +- MyBankApp/MyBankApp/SceneDelegate.swift | 10 +- 15 files changed, 284 insertions(+), 27 deletions(-) create mode 100644 MyBankApp/MyBankApp/Assets.xcassets/bankLogo.imageset/CDEBC6CD-73C4-47CB-B879-8A426D8163E1@1x.png create mode 100644 MyBankApp/MyBankApp/Assets.xcassets/bankLogo.imageset/CDEBC6CD-73C4-47CB-B879-8A426D8163E1@2x.png create mode 100644 MyBankApp/MyBankApp/Assets.xcassets/bankLogo.imageset/CDEBC6CD-73C4-47CB-B879-8A426D8163E1@3x.png create mode 100644 MyBankApp/MyBankApp/Assets.xcassets/bankLogo.imageset/Contents.json create mode 100644 MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift create mode 100644 MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift create mode 100644 MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift create mode 100644 MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift create mode 100644 MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift create mode 100644 MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift rename MyBankApp/MyBankApp/Login/{ => Service}/LoginService.swift (72%) diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index 5ff9e799b..d298548e3 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -21,6 +21,12 @@ 5EEFAB6C2A50B16E00A000EE /* NetworkServiceSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB6B2A50B16E00A000EE /* NetworkServiceSpy.swift */; }; 5EEFAB6F2A50B21700A000EE /* LoginServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB6E2A50B21700A000EE /* LoginServiceTests.swift */; }; 5EEFAB722A50B80200A000EE /* HomeServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB712A50B80200A000EE /* HomeServiceTests.swift */; }; + 5EEFAB802A50CB8500A000EE /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB7F2A50CB8500A000EE /* LoginViewController.swift */; }; + 5EEFAB8F2A50CD8B00A000EE /* LoginProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB8E2A50CD8B00A000EE /* LoginProtocols.swift */; }; + 5EEFAB922A50D04B00A000EE /* LoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB912A50D04B00A000EE /* LoginInteractor.swift */; }; + 5EEFAB942A50D06700A000EE /* LoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB932A50D06700A000EE /* LoginPresenter.swift */; }; + 5EEFAB962A50D0C500A000EE /* LoginRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB952A50D0C500A000EE /* LoginRouter.swift */; }; + 5EEFAB982A50D15200A000EE /* LoginConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB972A50D15200A000EE /* LoginConfigurator.swift */; }; 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */; }; 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */; }; 5EF1ACB22A4E47C6002EA972 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB12A4E47C6002EA972 /* ViewController.swift */; }; @@ -66,6 +72,12 @@ 5EEFAB6B2A50B16E00A000EE /* NetworkServiceSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkServiceSpy.swift; sourceTree = ""; }; 5EEFAB6E2A50B21700A000EE /* LoginServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginServiceTests.swift; sourceTree = ""; }; 5EEFAB712A50B80200A000EE /* HomeServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeServiceTests.swift; sourceTree = ""; }; + 5EEFAB7F2A50CB8500A000EE /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + 5EEFAB8E2A50CD8B00A000EE /* LoginProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginProtocols.swift; sourceTree = ""; }; + 5EEFAB912A50D04B00A000EE /* LoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInteractor.swift; sourceTree = ""; }; + 5EEFAB932A50D06700A000EE /* LoginPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresenter.swift; sourceTree = ""; }; + 5EEFAB952A50D0C500A000EE /* LoginRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRouter.swift; sourceTree = ""; }; + 5EEFAB972A50D15200A000EE /* LoginConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginConfigurator.swift; sourceTree = ""; }; 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyBankApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -148,8 +160,9 @@ 5EEFAB5E2A50A8BE00A000EE /* Login */ = { isa = PBXGroup; children = ( + 5EEFAB902A50CECB00A000EE /* Service */, + 5EEFAB8D2A50CD7600A000EE /* Scene */, 5EEFAB652A50A99300A000EE /* Models */, - 5EEFAB562A50A81900A000EE /* LoginService.swift */, ); path = Login; sourceTree = ""; @@ -203,6 +216,27 @@ path = Home; sourceTree = ""; }; + 5EEFAB8D2A50CD7600A000EE /* Scene */ = { + isa = PBXGroup; + children = ( + 5EEFAB8E2A50CD8B00A000EE /* LoginProtocols.swift */, + 5EEFAB7F2A50CB8500A000EE /* LoginViewController.swift */, + 5EEFAB932A50D06700A000EE /* LoginPresenter.swift */, + 5EEFAB912A50D04B00A000EE /* LoginInteractor.swift */, + 5EEFAB952A50D0C500A000EE /* LoginRouter.swift */, + 5EEFAB972A50D15200A000EE /* LoginConfigurator.swift */, + ); + path = Scene; + sourceTree = ""; + }; + 5EEFAB902A50CECB00A000EE /* Service */ = { + isa = PBXGroup; + children = ( + 5EEFAB562A50A81900A000EE /* LoginService.swift */, + ); + path = Service; + sourceTree = ""; + }; 5EF1ACA12A4E47C6002EA972 = { isa = PBXGroup; children = ( @@ -520,14 +554,20 @@ files = ( 5EEFAB622A50A8E500A000EE /* HomeResponse.swift in Sources */, 5EEFAB5D2A50A88600A000EE /* NetworkServiceImpl.swift in Sources */, + 5EEFAB922A50D04B00A000EE /* LoginInteractor.swift in Sources */, 5EEFAB4E2A50982B00A000EE /* NetworkProtocols.swift in Sources */, + 5EEFAB802A50CB8500A000EE /* LoginViewController.swift in Sources */, 5EF1ACB22A4E47C6002EA972 /* ViewController.swift in Sources */, + 5EEFAB962A50D0C500A000EE /* LoginRouter.swift in Sources */, + 5EEFAB8F2A50CD8B00A000EE /* LoginProtocols.swift in Sources */, 5EEFAB642A50A8F900A000EE /* HomeService.swift in Sources */, 5EEFAB5B2A50A85B00A000EE /* HTTPMethod.swift in Sources */, + 5EEFAB942A50D06700A000EE /* LoginPresenter.swift in Sources */, 5EEFAB572A50A81900A000EE /* LoginService.swift in Sources */, 5EEFAB592A50A84300A000EE /* NetworkError.swift in Sources */, 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */, 5EEFAB672A50A99C00A000EE /* LoginResponse.swift in Sources */, + 5EEFAB982A50D15200A000EE /* LoginConfigurator.swift in Sources */, 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/MyBankApp/MyBankApp/AppDelegate.swift b/MyBankApp/MyBankApp/AppDelegate.swift index 3e5c2130e..e1a18e1a1 100644 --- a/MyBankApp/MyBankApp/AppDelegate.swift +++ b/MyBankApp/MyBankApp/AppDelegate.swift @@ -9,11 +9,14 @@ import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { - - - + + var window: UIWindow? + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + window = UIWindow(frame: UIScreen.main.bounds) + let rootViewController = LoginConfigurator().setup() // Set LoginViewController as the initial view controller + window?.rootViewController = rootViewController + window?.makeKeyAndVisible() return true } @@ -33,6 +36,5 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - } diff --git a/MyBankApp/MyBankApp/Assets.xcassets/bankLogo.imageset/CDEBC6CD-73C4-47CB-B879-8A426D8163E1@1x.png b/MyBankApp/MyBankApp/Assets.xcassets/bankLogo.imageset/CDEBC6CD-73C4-47CB-B879-8A426D8163E1@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..466ef1c0906562185bb5ba8296c63b43450b2101 GIT binary patch literal 4442 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 eA;!bID*Znm>^Im^04LS}0000&!iuanF6O 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

TE^z9Gd}Hf>Z@NDrGIIsaf@mMcYn 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 LylADcp8x*?ZG5E! literal 0 HcmV?d00001 diff --git a/MyBankApp/MyBankApp/Assets.xcassets/bankLogo.imageset/Contents.json b/MyBankApp/MyBankApp/Assets.xcassets/bankLogo.imageset/Contents.json new file mode 100644 index 000000000..4cf613711 --- /dev/null +++ b/MyBankApp/MyBankApp/Assets.xcassets/bankLogo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "CDEBC6CD-73C4-47CB-B879-8A426D8163E1@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "CDEBC6CD-73C4-47CB-B879-8A426D8163E1@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "CDEBC6CD-73C4-47CB-B879-8A426D8163E1@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift b/MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift new file mode 100644 index 000000000..6cc0af07c --- /dev/null +++ b/MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift @@ -0,0 +1,16 @@ +import Foundation + +final class LoginConfigurator { + func setup() -> LoginViewController { + let viewController = LoginViewController() + let interactor = LoginInteractor() + let presenter = LoginPresenter() + let router = LoginRouter() + viewController.interactor = interactor + viewController.router = router + interactor.presenter = presenter + presenter.viewController = viewController + router.viewController = viewController + return viewController + } +} diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift new file mode 100644 index 000000000..1d2b13687 --- /dev/null +++ b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift @@ -0,0 +1,25 @@ +import Foundation + +final class LoginInteractor: LoginBusinessLogic { + var presenter: LoginPresentationLogic? + let loginService: LoginServiceProtocol + + init(presenter: LoginPresentationLogic? = nil, loginService: LoginServiceProtocol = LoginService()) { + self.presenter = presenter + self.loginService = loginService + } + + func login(username: String, password: String) { + presenter?.presentLoginSuccess(userId: "") +// loginService.login(user: username, password: password) { [presenter] result in +// switch result { +// case .success(let response): +// print("Logged in with ID:", response.id) +// presenter?.presentLoginSuccess(userId: response.id) +// case .failure(let error): +// print("Login error:", error.localizedDescription) +// presenter?.presentLoginError(message: "Invalid credentials") +// } +// } + } +} diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift b/MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift new file mode 100644 index 000000000..11c8c41fe --- /dev/null +++ b/MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift @@ -0,0 +1,13 @@ +import Foundation + +final class LoginPresenter: LoginPresentationLogic { + weak var viewController: LoginDisplayLogic? + + func presentLoginSuccess(userId: String) { + viewController?.displayLoginSuccess(userId: userId) + } + + func presentLoginError(message: String) { + viewController?.displayLoginError(message: message) + } +} diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift b/MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift new file mode 100644 index 000000000..a80fff9bc --- /dev/null +++ b/MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift @@ -0,0 +1,28 @@ +import Foundation + +// MARK: - LoginDisplayLogic +protocol LoginDisplayLogic: AnyObject { + func displayLoginSuccess(userId: String) + func displayLoginError(message: String) +} + +// MARK: - LoginInteractor +protocol LoginBusinessLogic { + func login(username: String, password: String) +} + +// MARK: - LoginPresenter +protocol LoginPresentationLogic { + func presentLoginSuccess(userId: String) + func presentLoginError(message: String) +} + +// MARK: - LoginRouter +protocol LoginRoutingLogic { + func routeToNextScene() +} + +// MARK: - LoginDataStore +protocol LoginDataStore { + var dataToPass: String? { get set } +} diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift b/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift new file mode 100644 index 000000000..5b3b868d2 --- /dev/null +++ b/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift @@ -0,0 +1,13 @@ +import UIKit + +final class LoginRouter: LoginRoutingLogic, LoginDataStore { + weak var viewController: LoginViewController? + var dataToPass: String? + + func routeToNextScene() { + let homeViewController = UIViewController() + //homeViewController.id = dataToPass + viewController?.navigationController?.pushViewController(homeViewController, animated: true) +// viewController?.present(homeViewController, animated: true) + } +} diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift new file mode 100644 index 000000000..88037c722 --- /dev/null +++ b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift @@ -0,0 +1,112 @@ +import UIKit + +final class LoginViewController: UIViewController { + var interactor: LoginBusinessLogic? + var router: (LoginRoutingLogic & LoginDataStore)? + + // MARK: UI Elements + private let imageView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(named: "bankLogo") + imageView.contentMode = .scaleAspectFit + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + private let usernameTextField: UITextField = { + let textField = UITextField() + textField.placeholder = "Username" + textField.borderStyle = .roundedRect + textField.translatesAutoresizingMaskIntoConstraints = false + return textField + }() + + private let passwordTextField: UITextField = { + let textField = UITextField() + textField.placeholder = "Password" + textField.borderStyle = .roundedRect + textField.isSecureTextEntry = true + textField.translatesAutoresizingMaskIntoConstraints = false + return textField + }() + + private let loginButton: UIButton = { + let button = UIButton() + button.setTitle("Login", for: .normal) + button.backgroundColor = .blue + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside) + return button + }() + + // MARK: View Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + } + + // MARK: UI Setup + private func setupUI() { + view.backgroundColor = .white + addSubviews() + setupImageViewConstraints() + setupStackViewConstraints() + setupLoginButtonConstraints() + } + + private func addSubviews() { + view.addSubview(imageView) + view.addSubview(usernameTextField) + view.addSubview(passwordTextField) + view.addSubview(loginButton) + } + + private func setupImageViewConstraints() { + imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true + imageView.heightAnchor.constraint(equalToConstant: 65).isActive = true + imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40).isActive = true + imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true + } + + private func setupStackViewConstraints() { + usernameTextField.heightAnchor.constraint(equalToConstant: 50).isActive = true + passwordTextField.heightAnchor.constraint(equalToConstant: 50).isActive = true + + let stackView = UIStackView(arrangedSubviews: [usernameTextField, passwordTextField]) + stackView.axis = .vertical + stackView.spacing = 16 + stackView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(stackView) + stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true + stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16).isActive = true + stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true + } + + private func setupLoginButtonConstraints() { + loginButton.heightAnchor.constraint(equalToConstant: 50).isActive = true + loginButton.widthAnchor.constraint(equalToConstant: 185).isActive = true + loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true + loginButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40).isActive = true + } + + // MARK: Actions + @objc private func loginButtonTapped() { + guard let username = usernameTextField.text, + let password = passwordTextField.text else { + return + } + + interactor?.login(username: username, password: password) + } +} + +extension LoginViewController: LoginDisplayLogic { + func displayLoginSuccess(userId: String) { + router?.dataToPass = userId + router?.routeToNextScene() + } + + func displayLoginError(message: String) { + + } +} diff --git a/MyBankApp/MyBankApp/Login/LoginService.swift b/MyBankApp/MyBankApp/Login/Service/LoginService.swift similarity index 72% rename from MyBankApp/MyBankApp/Login/LoginService.swift rename to MyBankApp/MyBankApp/Login/Service/LoginService.swift index 9836453c5..df41a9dc5 100644 --- a/MyBankApp/MyBankApp/Login/LoginService.swift +++ b/MyBankApp/MyBankApp/Login/Service/LoginService.swift @@ -32,18 +32,3 @@ final class LoginService: LoginServiceProtocol { } } } - -/* - let loginService = LoginService() - - loginService.login(user: "your-username", password: "your-password") { result in - switch result { - case .success(let response): - // Handle successful login - print("Logged in with ID:", response.id) - case .failure(let error): - // Handle login error - print("Login error:", error.localizedDescription) - } - } - */ diff --git a/MyBankApp/MyBankApp/Network/NetworkServiceImpl.swift b/MyBankApp/MyBankApp/Network/NetworkServiceImpl.swift index 791f2a45f..c63ed24e5 100644 --- a/MyBankApp/MyBankApp/Network/NetworkServiceImpl.swift +++ b/MyBankApp/MyBankApp/Network/NetworkServiceImpl.swift @@ -23,7 +23,7 @@ final class NetworkServiceImpl: NetworkService { let task = urlSession.dataTask(with: request) { data, response, error in // Check for network errors - if let error = error { + if let _ = error { completion(.failure(NetworkError.requestFailed)) return } diff --git a/MyBankApp/MyBankApp/SceneDelegate.swift b/MyBankApp/MyBankApp/SceneDelegate.swift index f4424c425..61ee739d1 100644 --- a/MyBankApp/MyBankApp/SceneDelegate.swift +++ b/MyBankApp/MyBankApp/SceneDelegate.swift @@ -11,13 +11,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - @available(iOS 13.0, *) func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } + guard let windowScene = (scene as? UIWindowScene) else { return } + window = UIWindow(windowScene: windowScene) + let rootViewController = LoginConfigurator().setup() + window?.rootViewController = rootViewController + window?.makeKeyAndVisible() } @available(iOS 13.0, *) From eb4eb6d4b8d0f06412f7b63e7959109dc50685ad Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Sun, 2 Jul 2023 13:10:21 -0300 Subject: [PATCH 09/27] =?UTF-8?q?Ajuste=20modelo=20LoginResponse.=20Ajuste?= =?UTF-8?q?=20modelo=20Clean,=20definindo=20o=20router.dataStore.=20Confor?= =?UTF-8?q?mando=20o=20LoginInteractor=20ao=20LoginDataStore.=20Ajustando?= =?UTF-8?q?=20protocolos=20e=20fun=C3=A7=C3=B5es=20para=20se=20adequar=20a?= =?UTF-8?q?o=20CleanSwift.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 2 +- .../MyBankApp/Login/Models/LoginResponse.swift | 8 +++++++- .../MyBankApp/Login/Scene/LoginConfigurator.swift | 1 + .../MyBankApp/Login/Scene/LoginInteractor.swift | 14 +++++++++----- .../MyBankApp/Login/Scene/LoginPresenter.swift | 4 ++-- .../MyBankApp/Login/Scene/LoginProtocols.swift | 13 +++++++++---- MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift | 13 +++++++------ .../Login/Scene/LoginViewController.swift | 7 +++---- 8 files changed, 39 insertions(+), 23 deletions(-) diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index d298548e3..c3bbee56a 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -262,8 +262,8 @@ 5EF1ACAC2A4E47C6002EA972 /* MyBankApp */ = { isa = PBXGroup; children = ( - 5EEFAB5F2A50A8CB00A000EE /* Home */, 5EEFAB5E2A50A8BE00A000EE /* Login */, + 5EEFAB5F2A50A8CB00A000EE /* Home */, 5EEFAB442A50739500A000EE /* Network */, 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */, 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */, diff --git a/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift b/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift index f7c4901b7..90ca57f08 100644 --- a/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift +++ b/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift @@ -1,5 +1,11 @@ import Foundation struct LoginResponse: Decodable { - let id: String + let userId: String + let email: String + let cpf: String + let name: String + let accountNumber: String + let agency: String + let balance: String } diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift b/MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift index 6cc0af07c..1c1b1a471 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift @@ -11,6 +11,7 @@ final class LoginConfigurator { interactor.presenter = presenter presenter.viewController = viewController router.viewController = viewController + router.dataStore = interactor return viewController } } diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift index 1d2b13687..43702737d 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift @@ -1,21 +1,25 @@ import Foundation -final class LoginInteractor: LoginBusinessLogic { +final class LoginInteractor: LoginBusinessLogic, LoginDataStore { var presenter: LoginPresentationLogic? let loginService: LoginServiceProtocol + var user: LoginResponse? + init(presenter: LoginPresentationLogic? = nil, loginService: LoginServiceProtocol = LoginService()) { self.presenter = presenter self.loginService = loginService } func login(username: String, password: String) { - presenter?.presentLoginSuccess(userId: "") -// loginService.login(user: username, password: password) { [presenter] result in + presenter?.presentLoginSuccess() +// loginService.login(user: username, password: password) { [weak self, presenter] result in // switch result { // case .success(let response): -// print("Logged in with ID:", response.id) -// presenter?.presentLoginSuccess(userId: response.id) +// guard let self = self else { return } +// print("Logged in with ID:", response.userId) +// user = response +// presenter?.presentLoginSuccess() // case .failure(let error): // print("Login error:", error.localizedDescription) // presenter?.presentLoginError(message: "Invalid credentials") diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift b/MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift index 11c8c41fe..4a455aff3 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift @@ -3,8 +3,8 @@ import Foundation final class LoginPresenter: LoginPresentationLogic { weak var viewController: LoginDisplayLogic? - func presentLoginSuccess(userId: String) { - viewController?.displayLoginSuccess(userId: userId) + func presentLoginSuccess() { + viewController?.displayLoginSuccess() } func presentLoginError(message: String) { diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift b/MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift index a80fff9bc..873ad7542 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift @@ -2,7 +2,7 @@ import Foundation // MARK: - LoginDisplayLogic protocol LoginDisplayLogic: AnyObject { - func displayLoginSuccess(userId: String) + func displayLoginSuccess() func displayLoginError(message: String) } @@ -13,16 +13,21 @@ protocol LoginBusinessLogic { // MARK: - LoginPresenter protocol LoginPresentationLogic { - func presentLoginSuccess(userId: String) + func presentLoginSuccess() func presentLoginError(message: String) } // MARK: - LoginRouter protocol LoginRoutingLogic { - func routeToNextScene() + func routeToHome() } // MARK: - LoginDataStore protocol LoginDataStore { - var dataToPass: String? { get set } + var user: LoginResponse? { get set } +} + +// MARK: - LoginDataPassing +protocol LoginDataPassing { + var dataStore: LoginDataStore? { get } } diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift b/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift index 5b3b868d2..29077e4b8 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift @@ -1,13 +1,14 @@ import UIKit -final class LoginRouter: LoginRoutingLogic, LoginDataStore { +final class LoginRouter: NSObject, LoginRoutingLogic, LoginDataPassing { weak var viewController: LoginViewController? - var dataToPass: String? + var dataStore: LoginDataStore? - func routeToNextScene() { + func routeToHome() { let homeViewController = UIViewController() - //homeViewController.id = dataToPass - viewController?.navigationController?.pushViewController(homeViewController, animated: true) -// viewController?.present(homeViewController, animated: true) +// let homeDataStore = homeViewController.router?.dataStore +// home.user = viewController?.user + viewController?.show(homeViewController, sender: nil) +// viewController?.navigationController?.pushViewController(homeViewController, animated: true) } } diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift index 88037c722..13f992200 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift @@ -2,7 +2,7 @@ import UIKit final class LoginViewController: UIViewController { var interactor: LoginBusinessLogic? - var router: (LoginRoutingLogic & LoginDataStore)? + var router: (NSObjectProtocol & LoginRoutingLogic & LoginDataPassing)? // MARK: UI Elements private let imageView: UIImageView = { @@ -101,9 +101,8 @@ final class LoginViewController: UIViewController { } extension LoginViewController: LoginDisplayLogic { - func displayLoginSuccess(userId: String) { - router?.dataToPass = userId - router?.routeToNextScene() + func displayLoginSuccess() { + router?.routeToHome() } func displayLoginError(message: String) { From e7165b741c0032e1abd3b7b21e3b28be75f757b7 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Sun, 2 Jul 2023 18:23:45 -0300 Subject: [PATCH 10/27] =?UTF-8?q?Ajustando=20AppDelegate=20e=20SceneDelega?= =?UTF-8?q?te=20para=20que=20iniciem=20o=20app=20com=20uma=20NavigationCon?= =?UTF-8?q?troller.=20Adicionando=20imagem=20do=20bot=C3=A3o=20de=20sair.?= =?UTF-8?q?=20Adicionando=20Extension=20de=20Double=20para=20formatar=20o?= =?UTF-8?q?=20valor=20da=20c=C3=A9lula.=20Adicionando=20Extension=20de=20S?= =?UTF-8?q?tring=20para=20formatar=20a=20data.=20Adicionando=20Extension?= =?UTF-8?q?=20de=20UIColor=20para=20utilizar=20hex=20color.=20Adicionando?= =?UTF-8?q?=20Extension=20de=20UIFont=20para=20facilitar=20o=20reuso=20das?= =?UTF-8?q?=20fontes=20do=20projeto.=20Adicionando=20StatementTableViewCel?= =?UTF-8?q?l.=20Criando=20tela=20da=20Home=20e=20ajustando=20a=20navega?= =?UTF-8?q?=C3=A7=C3=A3o=20da=20tela=20de=20Login=20para=20a=20Home.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 82 ++++++- MyBankApp/MyBankApp/AppDelegate.swift | 10 +- .../ic-exitButton.imageset/Contents.json | 21 ++ .../ic-exitButton.imageset/logout 2.png | Bin 0 -> 519 bytes .../Extensions/Double+Extension.swift | 11 + .../Extensions/String+Extension.swift | 18 ++ .../Extensions/UIColor+Extension.swift | 20 ++ .../Extensions/UIFont+Extension.swift | 13 + .../Home/Cells/StatementTableViewCell.swift | 107 +++++++++ .../Home/Scene/HomeConfigurator.swift | 17 ++ .../MyBankApp/Home/Scene/HomeInteractor.swift | 29 +++ .../MyBankApp/Home/Scene/HomePresenter.swift | 13 + .../MyBankApp/Home/Scene/HomeProtocols.swift | 33 +++ .../MyBankApp/Home/Scene/HomeRouter.swift | 13 + .../Home/Scene/HomeViewController.swift | 223 ++++++++++++++++++ .../Home/{ => Service}/HomeService.swift | 2 +- .../MyBankApp/Login/Scene/LoginRouter.swift | 19 +- MyBankApp/MyBankApp/SceneDelegate.swift | 9 +- MyBankApp/MyBankApp/ViewController.swift | 19 -- 19 files changed, 612 insertions(+), 47 deletions(-) create mode 100644 MyBankApp/MyBankApp/Assets.xcassets/ic-exitButton.imageset/Contents.json create mode 100644 MyBankApp/MyBankApp/Assets.xcassets/ic-exitButton.imageset/logout 2.png create mode 100644 MyBankApp/MyBankApp/Extensions/Double+Extension.swift create mode 100644 MyBankApp/MyBankApp/Extensions/String+Extension.swift create mode 100644 MyBankApp/MyBankApp/Extensions/UIColor+Extension.swift create mode 100644 MyBankApp/MyBankApp/Extensions/UIFont+Extension.swift create mode 100644 MyBankApp/MyBankApp/Home/Cells/StatementTableViewCell.swift create mode 100644 MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift create mode 100644 MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift create mode 100644 MyBankApp/MyBankApp/Home/Scene/HomePresenter.swift create mode 100644 MyBankApp/MyBankApp/Home/Scene/HomeProtocols.swift create mode 100644 MyBankApp/MyBankApp/Home/Scene/HomeRouter.swift create mode 100644 MyBankApp/MyBankApp/Home/Scene/HomeViewController.swift rename MyBankApp/MyBankApp/Home/{ => Service}/HomeService.swift (97%) delete mode 100644 MyBankApp/MyBankApp/ViewController.swift diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index c3bbee56a..b06f3255a 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -27,9 +27,19 @@ 5EEFAB942A50D06700A000EE /* LoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB932A50D06700A000EE /* LoginPresenter.swift */; }; 5EEFAB962A50D0C500A000EE /* LoginRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB952A50D0C500A000EE /* LoginRouter.swift */; }; 5EEFAB982A50D15200A000EE /* LoginConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB972A50D15200A000EE /* LoginConfigurator.swift */; }; + 5EEFABB42A51E56D00A000EE /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABB32A51E56D00A000EE /* UIColor+Extension.swift */; }; + 5EEFABB82A51E5CF00A000EE /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABB72A51E5CF00A000EE /* HomeViewController.swift */; }; + 5EEFABBA2A51E5F800A000EE /* HomeProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABB92A51E5F800A000EE /* HomeProtocols.swift */; }; + 5EEFABBC2A51E6C800A000EE /* HomePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABBB2A51E6C800A000EE /* HomePresenter.swift */; }; + 5EEFABBE2A51E71200A000EE /* HomeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABBD2A51E71200A000EE /* HomeInteractor.swift */; }; + 5EEFABC02A51EADB00A000EE /* HomeRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABBF2A51EADB00A000EE /* HomeRouter.swift */; }; + 5EEFABC22A51EB1D00A000EE /* HomeConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABC12A51EB1D00A000EE /* HomeConfigurator.swift */; }; + 5EEFABC72A52133B00A000EE /* StatementTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABC62A52133B00A000EE /* StatementTableViewCell.swift */; }; + 5EEFABD52A52179A00A000EE /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABD42A52179A00A000EE /* Double+Extension.swift */; }; + 5EEFABD72A52181C00A000EE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABD62A52181C00A000EE /* String+Extension.swift */; }; + 5EEFABD92A52195600A000EE /* UIFont+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */; }; 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */; }; 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */; }; - 5EF1ACB22A4E47C6002EA972 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB12A4E47C6002EA972 /* ViewController.swift */; }; 5EF1ACB52A4E47C6002EA972 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB32A4E47C6002EA972 /* Main.storyboard */; }; 5EF1ACB72A4E47C9002EA972 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB62A4E47C9002EA972 /* Assets.xcassets */; }; 5EF1ACBA2A4E47C9002EA972 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB82A4E47C9002EA972 /* LaunchScreen.storyboard */; }; @@ -78,10 +88,20 @@ 5EEFAB932A50D06700A000EE /* LoginPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresenter.swift; sourceTree = ""; }; 5EEFAB952A50D0C500A000EE /* LoginRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRouter.swift; sourceTree = ""; }; 5EEFAB972A50D15200A000EE /* LoginConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginConfigurator.swift; sourceTree = ""; }; + 5EEFABB32A51E56D00A000EE /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; + 5EEFABB72A51E5CF00A000EE /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; + 5EEFABB92A51E5F800A000EE /* HomeProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeProtocols.swift; sourceTree = ""; }; + 5EEFABBB2A51E6C800A000EE /* HomePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePresenter.swift; sourceTree = ""; }; + 5EEFABBD2A51E71200A000EE /* HomeInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeInteractor.swift; sourceTree = ""; }; + 5EEFABBF2A51EADB00A000EE /* HomeRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeRouter.swift; sourceTree = ""; }; + 5EEFABC12A51EB1D00A000EE /* HomeConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeConfigurator.swift; sourceTree = ""; }; + 5EEFABC62A52133B00A000EE /* StatementTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementTableViewCell.swift; sourceTree = ""; }; + 5EEFABD42A52179A00A000EE /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; + 5EEFABD62A52181C00A000EE /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; + 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extension.swift"; sourceTree = ""; }; 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyBankApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 5EF1ACB12A4E47C6002EA972 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 5EF1ACB42A4E47C6002EA972 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 5EF1ACB62A4E47C9002EA972 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 5EF1ACB92A4E47C9002EA972 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -170,8 +190,10 @@ 5EEFAB5F2A50A8CB00A000EE /* Home */ = { isa = PBXGroup; children = ( + 5EEFABC52A52132E00A000EE /* Cells */, + 5EEFABB62A51E5BD00A000EE /* Scene */, 5EEFAB602A50A8DE00A000EE /* Models */, - 5EEFAB632A50A8F900A000EE /* HomeService.swift */, + 5EEFABB52A51E5B100A000EE /* Service */, ); path = Home; sourceTree = ""; @@ -237,6 +259,46 @@ path = Service; sourceTree = ""; }; + 5EEFABB22A51E56300A000EE /* Extensions */ = { + isa = PBXGroup; + children = ( + 5EEFABB32A51E56D00A000EE /* UIColor+Extension.swift */, + 5EEFABD42A52179A00A000EE /* Double+Extension.swift */, + 5EEFABD62A52181C00A000EE /* String+Extension.swift */, + 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 5EEFABB52A51E5B100A000EE /* Service */ = { + isa = PBXGroup; + children = ( + 5EEFAB632A50A8F900A000EE /* HomeService.swift */, + ); + path = Service; + sourceTree = ""; + }; + 5EEFABB62A51E5BD00A000EE /* Scene */ = { + isa = PBXGroup; + children = ( + 5EEFABB92A51E5F800A000EE /* HomeProtocols.swift */, + 5EEFABB72A51E5CF00A000EE /* HomeViewController.swift */, + 5EEFABBB2A51E6C800A000EE /* HomePresenter.swift */, + 5EEFABBD2A51E71200A000EE /* HomeInteractor.swift */, + 5EEFABBF2A51EADB00A000EE /* HomeRouter.swift */, + 5EEFABC12A51EB1D00A000EE /* HomeConfigurator.swift */, + ); + path = Scene; + sourceTree = ""; + }; + 5EEFABC52A52132E00A000EE /* Cells */ = { + isa = PBXGroup; + children = ( + 5EEFABC62A52133B00A000EE /* StatementTableViewCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; 5EF1ACA12A4E47C6002EA972 = { isa = PBXGroup; children = ( @@ -262,12 +324,12 @@ 5EF1ACAC2A4E47C6002EA972 /* MyBankApp */ = { isa = PBXGroup; children = ( + 5EEFABB22A51E56300A000EE /* Extensions */, 5EEFAB5E2A50A8BE00A000EE /* Login */, 5EEFAB5F2A50A8CB00A000EE /* Home */, 5EEFAB442A50739500A000EE /* Network */, 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */, 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */, - 5EF1ACB12A4E47C6002EA972 /* ViewController.swift */, 5EF1ACB32A4E47C6002EA972 /* Main.storyboard */, 5EF1ACB62A4E47C9002EA972 /* Assets.xcassets */, 5EF1ACB82A4E47C9002EA972 /* LaunchScreen.storyboard */, @@ -552,21 +614,31 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5EEFABBC2A51E6C800A000EE /* HomePresenter.swift in Sources */, + 5EEFABBE2A51E71200A000EE /* HomeInteractor.swift in Sources */, 5EEFAB622A50A8E500A000EE /* HomeResponse.swift in Sources */, 5EEFAB5D2A50A88600A000EE /* NetworkServiceImpl.swift in Sources */, + 5EEFABD52A52179A00A000EE /* Double+Extension.swift in Sources */, + 5EEFABB82A51E5CF00A000EE /* HomeViewController.swift in Sources */, 5EEFAB922A50D04B00A000EE /* LoginInteractor.swift in Sources */, + 5EEFABB42A51E56D00A000EE /* UIColor+Extension.swift in Sources */, + 5EEFABC72A52133B00A000EE /* StatementTableViewCell.swift in Sources */, 5EEFAB4E2A50982B00A000EE /* NetworkProtocols.swift in Sources */, 5EEFAB802A50CB8500A000EE /* LoginViewController.swift in Sources */, - 5EF1ACB22A4E47C6002EA972 /* ViewController.swift in Sources */, + 5EEFABD72A52181C00A000EE /* String+Extension.swift in Sources */, 5EEFAB962A50D0C500A000EE /* LoginRouter.swift in Sources */, + 5EEFABC02A51EADB00A000EE /* HomeRouter.swift in Sources */, 5EEFAB8F2A50CD8B00A000EE /* LoginProtocols.swift in Sources */, + 5EEFABD92A52195600A000EE /* UIFont+Extension.swift in Sources */, 5EEFAB642A50A8F900A000EE /* HomeService.swift in Sources */, 5EEFAB5B2A50A85B00A000EE /* HTTPMethod.swift in Sources */, 5EEFAB942A50D06700A000EE /* LoginPresenter.swift in Sources */, 5EEFAB572A50A81900A000EE /* LoginService.swift in Sources */, 5EEFAB592A50A84300A000EE /* NetworkError.swift in Sources */, 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */, + 5EEFABBA2A51E5F800A000EE /* HomeProtocols.swift in Sources */, 5EEFAB672A50A99C00A000EE /* LoginResponse.swift in Sources */, + 5EEFABC22A51EB1D00A000EE /* HomeConfigurator.swift in Sources */, 5EEFAB982A50D15200A000EE /* LoginConfigurator.swift in Sources */, 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */, ); diff --git a/MyBankApp/MyBankApp/AppDelegate.swift b/MyBankApp/MyBankApp/AppDelegate.swift index e1a18e1a1..5353f0413 100644 --- a/MyBankApp/MyBankApp/AppDelegate.swift +++ b/MyBankApp/MyBankApp/AppDelegate.swift @@ -1,10 +1,3 @@ -// -// AppDelegate.swift -// MyBankApp -// -// Created by Matheus Fusco on 29/06/23. -// - import UIKit @main @@ -14,14 +7,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) - let rootViewController = LoginConfigurator().setup() // Set LoginViewController as the initial view controller + let rootViewController = UINavigationController(rootViewController: LoginConfigurator().setup()) window?.rootViewController = rootViewController window?.makeKeyAndVisible() return true } // MARK: UISceneSession Lifecycle - @available(iOS 13.0, *) func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. diff --git a/MyBankApp/MyBankApp/Assets.xcassets/ic-exitButton.imageset/Contents.json b/MyBankApp/MyBankApp/Assets.xcassets/ic-exitButton.imageset/Contents.json new file mode 100644 index 000000000..91fdf6f86 --- /dev/null +++ b/MyBankApp/MyBankApp/Assets.xcassets/ic-exitButton.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "logout 2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MyBankApp/MyBankApp/Assets.xcassets/ic-exitButton.imageset/logout 2.png b/MyBankApp/MyBankApp/Assets.xcassets/ic-exitButton.imageset/logout 2.png new file mode 100644 index 0000000000000000000000000000000000000000..de1e4ae3c001abc798483e59649cc97eb9661201 GIT binary patch literal 519 zcmV+i0{H!jP)Px$!AV3xR7efImcK6qK@`Vt&yOpJAaaRoh+Gu3D$TW8iAJdse}KjxK(rcBh=xul zAwh`5X@pLr5eZjF9Io*rVtwA46`P&C8*9c(J~z8>-n{S5%-gxmWR&^7pM?@^Kn3dA z4xMe`Sktyy0vVX4nSfQO!X+Go&RTGm;Z4`s(1+Lv+`yq+Hyn^QfaEz?fm`TLo5y6F zEME>r;R3F~nTeR}TLEgG=lRZ*NGxJv-dE%HHx#p?!k%|QF)5ilNtW}ZoK0DPBTS`Z-PCFK@Qk-CW24`lP_Y435&~E@Dm&wyf7;HNhSaQ002ov JPDHLkV1oP+;TQk_ literal 0 HcmV?d00001 diff --git a/MyBankApp/MyBankApp/Extensions/Double+Extension.swift b/MyBankApp/MyBankApp/Extensions/Double+Extension.swift new file mode 100644 index 000000000..ce6385c08 --- /dev/null +++ b/MyBankApp/MyBankApp/Extensions/Double+Extension.swift @@ -0,0 +1,11 @@ +import UIKit + +extension Double { + func formatCurrency() -> String? { + let formatter = NumberFormatter() + formatter.numberStyle = .currency + formatter.locale = Locale(identifier: "pt_BR") + + return formatter.string(from: NSNumber(value: self)) + } +} diff --git a/MyBankApp/MyBankApp/Extensions/String+Extension.swift b/MyBankApp/MyBankApp/Extensions/String+Extension.swift new file mode 100644 index 000000000..8d4a8d537 --- /dev/null +++ b/MyBankApp/MyBankApp/Extensions/String+Extension.swift @@ -0,0 +1,18 @@ +import UIKit + +extension String { + func convertToValidDate() -> String? { + let inputFormatter = DateFormatter() + inputFormatter.dateFormat = "ddMMyyyy" + + guard let date = inputFormatter.date(from: self) else { + return nil // Unable to parse the input date string + } + + let outputFormatter = DateFormatter() + outputFormatter.dateFormat = "dd/MM/yyyy" + + let formattedDate = outputFormatter.string(from: date) + return formattedDate + } +} diff --git a/MyBankApp/MyBankApp/Extensions/UIColor+Extension.swift b/MyBankApp/MyBankApp/Extensions/UIColor+Extension.swift new file mode 100644 index 000000000..b6fd76a34 --- /dev/null +++ b/MyBankApp/MyBankApp/Extensions/UIColor+Extension.swift @@ -0,0 +1,20 @@ +import UIKit + +extension UIColor { + convenience init(hex: String) { + var hexFormatted: String = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased() + + if hexFormatted.hasPrefix("#") { + hexFormatted = String(hexFormatted.dropFirst()) + } + + var rgbValue: UInt64 = 0 + Scanner(string: hexFormatted).scanHexInt64(&rgbValue) + + let r = (rgbValue & 0xFF0000) >> 16 + let g = (rgbValue & 0x00FF00) >> 8 + let b = rgbValue & 0x0000FF + + self.init(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1.0) + } +} diff --git a/MyBankApp/MyBankApp/Extensions/UIFont+Extension.swift b/MyBankApp/MyBankApp/Extensions/UIFont+Extension.swift new file mode 100644 index 000000000..4554537b7 --- /dev/null +++ b/MyBankApp/MyBankApp/Extensions/UIFont+Extension.swift @@ -0,0 +1,13 @@ +import UIKit + +extension UIFont { + + func set(_ fontName: MyBankFont, size: CGFloat) -> UIFont { + return UIFont(name: fontName.rawValue, size: size) ?? UIFont(name: "Arial", size: size)! + } + + enum MyBankFont: String { + case HelveticaNeue = "HelveticaNeue" + case HelveticaNeueLight = "HelveticaNeue-Light" + } +} diff --git a/MyBankApp/MyBankApp/Home/Cells/StatementTableViewCell.swift b/MyBankApp/MyBankApp/Home/Cells/StatementTableViewCell.swift new file mode 100644 index 000000000..7700f6457 --- /dev/null +++ b/MyBankApp/MyBankApp/Home/Cells/StatementTableViewCell.swift @@ -0,0 +1,107 @@ +import UIKit + +final class StatementCell: UITableViewCell { + private let backgroundContainerView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.layer.cornerRadius = 8 + return view + }() + + private let typeLabel: UILabel = { + let label = UILabel() + label.font = UIFont().set(.HelveticaNeue, size: 16) + label.textColor = UIColor(hex: "#A8B4C4") + return label + }() + + private let detailLabel: UILabel = { + let label = UILabel() + label.font = UIFont().set(.HelveticaNeue, size: 16) + label.textColor = UIColor(hex: "#485465") + label.numberOfLines = 0 + return label + }() + + private let dateLabel: UILabel = { + let label = UILabel() + label.font = UIFont().set(.HelveticaNeue, size: 16) + label.textColor = UIColor(hex: "#A8B4C4") + return label + }() + + private let valueLabel: UILabel = { + let label = UILabel() + label.font = UIFont().set(.HelveticaNeue, size: 20) + label.textColor = UIColor(hex: "#485465") + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + addSubviews() + setupConstraints() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(with statement: Statement) { + typeLabel.text = statement.type + detailLabel.text = statement.detail + dateLabel.text = statement.date.convertToValidDate() + valueLabel.text = statement.value.formatCurrency() + } + + private func addSubviews() { + contentView.addSubview(backgroundContainerView) + backgroundContainerView.addSubview(typeLabel) + backgroundContainerView.addSubview(detailLabel) + backgroundContainerView.addSubview(dateLabel) + backgroundContainerView.addSubview(valueLabel) + } + + private func setupConstraints() { + setupBackgroundContainerViewConstraints() + setupTypeLabelConstraints() + setupDetailLabelConstraints() + setupDateLabelConstraints() + setupValueLabelConstraints() + } + + private func setupBackgroundContainerViewConstraints() { + backgroundContainerView.translatesAutoresizingMaskIntoConstraints = false + backgroundContainerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8).isActive = true + backgroundContainerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8).isActive = true + backgroundContainerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8).isActive = true + backgroundContainerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8).isActive = true + } + + private func setupTypeLabelConstraints() { + typeLabel.translatesAutoresizingMaskIntoConstraints = false + typeLabel.topAnchor.constraint(equalTo: backgroundContainerView.topAnchor, constant: 18).isActive = true + typeLabel.leadingAnchor.constraint(equalTo: backgroundContainerView.leadingAnchor, constant: 20).isActive = true + } + + private func setupDetailLabelConstraints() { + detailLabel.translatesAutoresizingMaskIntoConstraints = false + detailLabel.topAnchor.constraint(equalTo: typeLabel.bottomAnchor, constant: 18).isActive = true + detailLabel.leadingAnchor.constraint(equalTo: backgroundContainerView.leadingAnchor, constant: 20).isActive = true + detailLabel.trailingAnchor.constraint(equalTo: backgroundContainerView.trailingAnchor, constant: -20).isActive = true + } + + private func setupDateLabelConstraints() { + dateLabel.translatesAutoresizingMaskIntoConstraints = false + dateLabel.topAnchor.constraint(equalTo: backgroundContainerView.topAnchor, constant: 18).isActive = true + dateLabel.trailingAnchor.constraint(equalTo: backgroundContainerView.trailingAnchor, constant: -20).isActive = true + } + + private func setupValueLabelConstraints() { + valueLabel.translatesAutoresizingMaskIntoConstraints = false + valueLabel.topAnchor.constraint(equalTo: dateLabel.bottomAnchor, constant: 13).isActive = true + valueLabel.trailingAnchor.constraint(equalTo: backgroundContainerView.trailingAnchor, constant: -20).isActive = true + valueLabel.bottomAnchor.constraint(equalTo: backgroundContainerView.bottomAnchor, constant: -18).isActive = true + } +} diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift b/MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift new file mode 100644 index 000000000..cf95489c6 --- /dev/null +++ b/MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift @@ -0,0 +1,17 @@ +import Foundation + +final class HomeConfigurator { + func setup() -> HomeViewController { + let viewController = HomeViewController() + let interactor = HomeInteractor() + let presenter = HomePresenter() + let router = HomeRouter() + viewController.interactor = interactor + viewController.router = router + interactor.presenter = presenter + presenter.viewController = viewController + router.viewController = viewController + router.dataStore = interactor + return viewController + } +} diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift b/MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift new file mode 100644 index 000000000..ee02d69a8 --- /dev/null +++ b/MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift @@ -0,0 +1,29 @@ +import Foundation + +final class HomeInteractor: HomeBusinessLogic, HomeDataStore { + + var user: LoginResponse? + + var presenter: HomePresentationLogic? + let homeService: HomeServiceProtocol + + init(presenter: HomePresentationLogic? = nil, homeService: HomeServiceProtocol = HomeService()) { + self.presenter = presenter + self.homeService = homeService + } + + func fetchUserStatements() { + homeService.fetchStatements(for: user?.userId ?? "") { [weak self, presenter] result in + switch result { + case .success(let response): + guard let self = self else { return } + print("Statements:", response.statement) +// statements = response.statement + presenter?.presentStatementsSuccess(response.statement) + case .failure(let error): + print("Statements error:", error.localizedDescription) + presenter?.presentStatementsError(message: "") + } + } + } +} diff --git a/MyBankApp/MyBankApp/Home/Scene/HomePresenter.swift b/MyBankApp/MyBankApp/Home/Scene/HomePresenter.swift new file mode 100644 index 000000000..5b4f99281 --- /dev/null +++ b/MyBankApp/MyBankApp/Home/Scene/HomePresenter.swift @@ -0,0 +1,13 @@ +import Foundation + +final class HomePresenter: HomePresentationLogic { + weak var viewController: HomeDisplayLogic? + + func presentStatementsSuccess(_ statements: [Statement]) { + viewController?.displayStatements(statements) + } + + func presentStatementsError(message: String) { + viewController?.displayStatementsError(message: message) + } +} diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeProtocols.swift b/MyBankApp/MyBankApp/Home/Scene/HomeProtocols.swift new file mode 100644 index 000000000..d77020a6f --- /dev/null +++ b/MyBankApp/MyBankApp/Home/Scene/HomeProtocols.swift @@ -0,0 +1,33 @@ +import Foundation + +// MARK: - HomeDisplayLogic +protocol HomeDisplayLogic: AnyObject { + func displayStatements(_ statements: [Statement]) + func displayStatementsError(message: String) +} + +// MARK: - HomeInteractor +protocol HomeBusinessLogic { + func fetchUserStatements() +} + +// MARK: - HomePresenter +protocol HomePresentationLogic { + func presentStatementsSuccess(_ statements: [Statement]) + func presentStatementsError(message: String) +} + +// MARK: - HomeRouter +protocol HomeRoutingLogic { + func logout() +} + +// MARK: - HomeDataStore +protocol HomeDataStore { + var user: LoginResponse? { get set } +} + +// MARK: - LoginDataPassing +protocol HomeDataPassing { + var dataStore: HomeDataStore? { get } +} diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeRouter.swift b/MyBankApp/MyBankApp/Home/Scene/HomeRouter.swift new file mode 100644 index 000000000..59e5535d5 --- /dev/null +++ b/MyBankApp/MyBankApp/Home/Scene/HomeRouter.swift @@ -0,0 +1,13 @@ +import UIKit + +final class HomeRouter: NSObject, HomeRoutingLogic, HomeDataPassing { + weak var viewController: HomeViewController? + var dataStore: HomeDataStore? + + func logout() { + viewController?.navigationController?.popViewController(animated: true) + } +// func routeToStatementDetail() { +// +// } +} diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeViewController.swift b/MyBankApp/MyBankApp/Home/Scene/HomeViewController.swift new file mode 100644 index 000000000..af215d94a --- /dev/null +++ b/MyBankApp/MyBankApp/Home/Scene/HomeViewController.swift @@ -0,0 +1,223 @@ +import UIKit + +final class HomeViewController: UIViewController { + private let containerView = UIView() + private let blueSquareView = UIView() + private let exitButton = UIButton(type: .system) + private let nameLabel = UILabel() + private let accountLabel = UILabel() + private let accountNumberLabel = UILabel() + private let balanceLabel = UILabel() + private let balanceValueLabel = UILabel() + private let tableView = UITableView() + + var interactor: HomeBusinessLogic? + var router: (NSObjectProtocol & HomeRoutingLogic & HomeDataPassing)? + + private var statements: [Statement] = [] + + override func viewDidLoad() { + super.viewDidLoad() + navigationController?.interactivePopGestureRecognizer?.isEnabled = false + setupViews() + setupUserInfo() + interactor?.fetchUserStatements() + } + + // MARK: SetupUI + private func addSubviews() { + view.addSubview(containerView) + containerView.addSubview(blueSquareView) + containerView.addSubview(exitButton) + containerView.addSubview(nameLabel) + containerView.addSubview(accountLabel) + containerView.addSubview(accountNumberLabel) + containerView.addSubview(balanceLabel) + containerView.addSubview(balanceValueLabel) + containerView.addSubview(tableView) + } + + private func setupContainerView() { + containerView.translatesAutoresizingMaskIntoConstraints = false + containerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true + containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true + containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + } + + private func setupSquareView() { + blueSquareView.translatesAutoresizingMaskIntoConstraints = false + blueSquareView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true + blueSquareView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true + blueSquareView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true + blueSquareView.heightAnchor.constraint(equalToConstant: 250).isActive = true + blueSquareView.backgroundColor = UIColor(hex: "#3B49EE") + } + + private func setupExitButton() { + exitButton.translatesAutoresizingMaskIntoConstraints = false + exitButton.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 60).isActive = true + exitButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20).isActive = true + exitButton.widthAnchor.constraint(equalToConstant: 54).isActive = true + exitButton.heightAnchor.constraint(equalToConstant: 54).isActive = true + exitButton.setImage(UIImage(named: "ic-exitButton"), for: .normal) + exitButton.tintColor = UIColor(hex: "#FFFFFF") + if #available(iOS 14.0, *) { + exitButton.addAction(UIAction(handler: { [weak self] _ in + self?.exitButtonTapped() + }), for: .touchUpInside) + } else { + // Check if iOS version is 11 or earlier + if #available(iOS 12.0, *) { + exitButton.addTarget(self, action: #selector(exitButtonTapped), for: .touchUpInside) + } else { + // Set a fallback action for iOS versions 11 or earlier + exitButton.addTarget(self, action: #selector(exitButtonTapped), for: .touchDown) + } + } + } + + private func setupUserNameLabel() { + nameLabel.font = UIFont().set(.HelveticaNeueLight, size: 25) + nameLabel.textColor = UIColor(hex: "#FFFFFF") + nameLabel.text = "-" + } + + private func setupUserNameLabelConstraints() { + nameLabel.translatesAutoresizingMaskIntoConstraints = false + nameLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 60).isActive = true + nameLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 18).isActive = true + nameLabel.trailingAnchor.constraint(equalTo: exitButton.leadingAnchor, constant: -18).isActive = true + nameLabel.heightAnchor.constraint(equalToConstant: 29).isActive = true + } + + private func setupAccountLabel() { + accountLabel.font = UIFont().set(.HelveticaNeue, size: 13) + accountLabel.textColor = UIColor(hex: "#FFFFFF") + accountLabel.text = "Conta" + } + + private func setupAccountLabelConstraints() { + accountLabel.translatesAutoresizingMaskIntoConstraints = false + accountLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 28).isActive = true + accountLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 18).isActive = true + accountLabel.heightAnchor.constraint(equalToConstant: 13).isActive = true + } + + private func setupAccountNumberLabel() { + accountNumberLabel.font = UIFont().set(.HelveticaNeueLight, size: 25) + accountNumberLabel.textColor = UIColor(hex: "#FFFFFF") + } + + private func setupAccountNumberLabelConstraints() { + accountNumberLabel.translatesAutoresizingMaskIntoConstraints = false + accountNumberLabel.topAnchor.constraint(equalTo: accountLabel.bottomAnchor, constant: 6).isActive = true + accountNumberLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 18).isActive = true + accountNumberLabel.heightAnchor.constraint(equalToConstant: 29).isActive = true + } + + private func setupBalanceLabel() { + balanceLabel.font = UIFont().set(.HelveticaNeue, size: 13) + balanceLabel.textColor = UIColor(hex: "#FFFFFF") + balanceLabel.text = "Saldo" + } + + private func setupBalanceLabelConstraints() { + balanceLabel.translatesAutoresizingMaskIntoConstraints = false + balanceLabel.topAnchor.constraint(equalTo: accountNumberLabel.bottomAnchor, constant: 21).isActive = true + balanceLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 18).isActive = true + balanceLabel.heightAnchor.constraint(equalToConstant: 13).isActive = true + } + + private func setupBalanceValueLabel() { + balanceValueLabel.font = UIFont().set(.HelveticaNeueLight, size: 25) + balanceValueLabel.textColor = UIColor(hex: "#FFFFFF") + } + + private func setupBalanceValueLabelConstraints() { + balanceValueLabel.translatesAutoresizingMaskIntoConstraints = false + balanceValueLabel.topAnchor.constraint(equalTo: balanceLabel.bottomAnchor, constant: 6).isActive = true + balanceValueLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 18).isActive = true + balanceValueLabel.heightAnchor.constraint(equalToConstant: 29).isActive = true + } + + private func setupTableView() { + tableView.delegate = self + tableView.dataSource = self + tableView.register(StatementCell.self, forCellReuseIdentifier: "StatementCell") + } + + private func setupTableViewConstraints() { + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.topAnchor.constraint(equalTo: blueSquareView.bottomAnchor).isActive = true + tableView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true + tableView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true + tableView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true + } + + private func setupViews() { + addSubviews() + setupContainerView() + setupSquareView() + setupExitButton() + setupUserNameLabel() + setupUserNameLabelConstraints() + setupAccountLabel() + setupAccountLabelConstraints() + setupAccountNumberLabel() + setupAccountNumberLabelConstraints() + setupBalanceLabel() + setupBalanceLabelConstraints() + setupBalanceValueLabel() + setupBalanceValueLabelConstraints() + setupTableView() + setupTableViewConstraints() + } + + private func setupUserInfo() { + guard let user = router?.dataStore?.user else { return } + nameLabel.text = user.name + accountNumberLabel.text = user.accountNumber + balanceValueLabel.text = user.balance + } + + @objc private func exitButtonTapped() { + router?.logout() + } +} + +// MARK: - HomeDisplayLogic Methods +extension HomeViewController: HomeDisplayLogic { + func displayStatements(_ statements: [Statement]) { + DispatchQueue.main.async { + self.statements = statements + self.tableView.reloadData() + } + } + + func displayStatementsError(message: String) { + + } +} + +// MARK: - UITableView Methods +extension HomeViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return statements.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "StatementCell", for: indexPath) as? StatementCell else { + fatalError("Failed to dequeue StatementCell") + } + + let statement = statements[indexPath.row] + cell.configure(with: statement) + + return cell + } +} diff --git a/MyBankApp/MyBankApp/Home/HomeService.swift b/MyBankApp/MyBankApp/Home/Service/HomeService.swift similarity index 97% rename from MyBankApp/MyBankApp/Home/HomeService.swift rename to MyBankApp/MyBankApp/Home/Service/HomeService.swift index ef9677974..1b89c5cb2 100644 --- a/MyBankApp/MyBankApp/Home/HomeService.swift +++ b/MyBankApp/MyBankApp/Home/Service/HomeService.swift @@ -15,7 +15,7 @@ final class HomeService: HomeServiceProtocol { for id: String, completion: @escaping (Result) -> Void ) { - guard let url = URL(string: "https://6092aef785ff5100172136c2.mockapi.io/api/statements/\(id)") else { + guard let url = URL(string: "https://6092aef785ff5100172136c2.mockapi.io/api/statements") else { completion(.failure(.invalidResponse)) return } diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift b/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift index 29077e4b8..3ee2167a0 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift @@ -5,10 +5,19 @@ final class LoginRouter: NSObject, LoginRoutingLogic, LoginDataPassing { var dataStore: LoginDataStore? func routeToHome() { - let homeViewController = UIViewController() -// let homeDataStore = homeViewController.router?.dataStore -// home.user = viewController?.user - viewController?.show(homeViewController, sender: nil) -// viewController?.navigationController?.pushViewController(homeViewController, animated: true) + let homeViewController = HomeConfigurator().setup() + var homeDataStore = homeViewController.router?.dataStore + let mockUserResponse = LoginResponse( + userId: "1", + email: "jose_da_silta@teste.com6", + cpf: "123.456.789-10", + name: "José da Silva Teste", + accountNumber: "2050 / 01.111222-4", + agency: "0", + balance: "R$ 1.000,00" + ) + homeDataStore?.user = mockUserResponse//dataStore?.user + homeViewController.navigationItem.setHidesBackButton(true, animated: false) + viewController?.navigationController?.pushViewController(homeViewController, animated: true) } } diff --git a/MyBankApp/MyBankApp/SceneDelegate.swift b/MyBankApp/MyBankApp/SceneDelegate.swift index 61ee739d1..902d4c68d 100644 --- a/MyBankApp/MyBankApp/SceneDelegate.swift +++ b/MyBankApp/MyBankApp/SceneDelegate.swift @@ -1,10 +1,3 @@ -// -// SceneDelegate.swift -// MyBankApp -// -// Created by Matheus Fusco on 29/06/23. -// - import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -15,7 +8,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) - let rootViewController = LoginConfigurator().setup() + let rootViewController = UINavigationController(rootViewController: LoginConfigurator().setup()) window?.rootViewController = rootViewController window?.makeKeyAndVisible() } diff --git a/MyBankApp/MyBankApp/ViewController.swift b/MyBankApp/MyBankApp/ViewController.swift deleted file mode 100644 index 2ec295598..000000000 --- a/MyBankApp/MyBankApp/ViewController.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// ViewController.swift -// MyBankApp -// -// Created by Matheus Fusco on 29/06/23. -// - -import UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - } - - -} - From ab7cdefb939df3e54dc354e86abf2aea50acb6d9 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Sun, 2 Jul 2023 18:24:51 -0300 Subject: [PATCH 11/27] =?UTF-8?q?Removendo=20pods=20que=20acabei=20n=C3=A3?= =?UTF-8?q?o=20usando=20no=20projeto.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 36 ------------------- MyBankApp/Podfile | 6 ++-- MyBankApp/Podfile.lock | 18 +--------- 3 files changed, 4 insertions(+), 56 deletions(-) diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index b06f3255a..0866fd006 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -368,7 +368,6 @@ 5EF1ACA62A4E47C6002EA972 /* Sources */, 5EF1ACA72A4E47C6002EA972 /* Frameworks */, 5EF1ACA82A4E47C6002EA972 /* Resources */, - D0BC799AEC15A7F58CA95A14 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -407,7 +406,6 @@ 5EF1ACC62A4E47C9002EA972 /* Sources */, 5EF1ACC72A4E47C9002EA972 /* Frameworks */, 5EF1ACC82A4E47C9002EA972 /* Resources */, - 5283A1C67CEE7CC88BFA9EAF /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -512,23 +510,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 5283A1C67CEE7CC88BFA9EAF /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; B586E5958C28FD1C7110E56C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -590,23 +571,6 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MyBankAppTests/Pods-MyBankAppTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - D0BC799AEC15A7F58CA95A14 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-MyBankApp/Pods-MyBankApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-MyBankApp/Pods-MyBankApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MyBankApp/Pods-MyBankApp-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/MyBankApp/Podfile b/MyBankApp/Podfile index b2373787f..ba7b8a807 100644 --- a/MyBankApp/Podfile +++ b/MyBankApp/Podfile @@ -6,9 +6,9 @@ target 'MyBankApp' do use_frameworks! # Pods for MyBankApp - pod 'SnapKit' - pod 'PromiseKit' - + # + # + target 'MyBankAppTests' do inherit! :search_paths # Pods for testing diff --git a/MyBankApp/Podfile.lock b/MyBankApp/Podfile.lock index c9185f0c7..2e169087f 100644 --- a/MyBankApp/Podfile.lock +++ b/MyBankApp/Podfile.lock @@ -1,32 +1,16 @@ PODS: - - PromiseKit (8.0.0): - - PromiseKit/CorePromise (= 8.0.0) - - PromiseKit/Foundation (= 8.0.0) - - PromiseKit/UIKit (= 8.0.0) - - PromiseKit/CorePromise (8.0.0) - - PromiseKit/Foundation (8.0.0): - - PromiseKit/CorePromise - - PromiseKit/UIKit (8.0.0): - - PromiseKit/CorePromise - - SnapKit (5.6.0) - SnapshotTesting (1.9.0) DEPENDENCIES: - - PromiseKit - - SnapKit - SnapshotTesting SPEC REPOS: trunk: - - PromiseKit - - SnapKit - SnapshotTesting SPEC CHECKSUMS: - PromiseKit: 7039707ba8764b6ce98501debce2489561b038a5 - SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25 SnapshotTesting: 6141c48b6aa76ead61431ca665c14ab9a066c53b -PODFILE CHECKSUM: 385464d08169c5d2af06404b0b805446588bbf79 +PODFILE CHECKSUM: ba0d6c1df54ed8d5dfdaf032779ee7c6deff107b COCOAPODS: 1.12.1 From fda342dcb6f53580ccc93c8211ae34ab20db4155 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Sun, 2 Jul 2023 19:01:03 -0300 Subject: [PATCH 12/27] =?UTF-8?q?Ajustando=20testes=20unit=C3=A1rios=20que?= =?UTF-8?q?=20estavam=20falhando=20ap=C3=B3s=20a=20troca=20da=20URL=20de?= =?UTF-8?q?=20statements,=20e=20do=20modelo=20de=20resposta=20da=20LoginSe?= =?UTF-8?q?rvice.=20protocol=20DispatchQueueProtocol=20e=20respectivo=20sp?= =?UTF-8?q?y=20criado=20para=20auxiliar=20no=20futuro=20teste=20da=20HomeV?= =?UTF-8?q?iewController.=20Init=20da=20HomeViewController=20ajustado=20pa?= =?UTF-8?q?ra=20poder=20ficar=20test=C3=A1vel.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 16 +++++++++++++ .../Extensions/DispatchQueue+Extension.swift | 11 +++++++++ .../MyBankApp/Home/Scene/HomeRouter.swift | 3 --- .../Home/Scene/HomeViewController.swift | 14 ++++++++++- .../MyBankApp/Home/Service/HomeService.swift | 23 +----------------- .../Common/DispatchQueueSpy.swift | 9 +++++++ .../Home/HomeServiceTests.swift | 4 ++-- .../Login/LoginServiceTests.swift | 24 +++++++++++++++++-- 8 files changed, 74 insertions(+), 30 deletions(-) create mode 100644 MyBankApp/MyBankApp/Extensions/DispatchQueue+Extension.swift create mode 100644 MyBankApp/MyBankAppTests/Common/DispatchQueueSpy.swift diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index 0866fd006..a939146d8 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -38,6 +38,8 @@ 5EEFABD52A52179A00A000EE /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABD42A52179A00A000EE /* Double+Extension.swift */; }; 5EEFABD72A52181C00A000EE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABD62A52181C00A000EE /* String+Extension.swift */; }; 5EEFABD92A52195600A000EE /* UIFont+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */; }; + 5EEFABDB2A5226CC00A000EE /* DispatchQueue+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABDA2A5226CC00A000EE /* DispatchQueue+Extension.swift */; }; + 5EEFABDE2A522A2B00A000EE /* DispatchQueueSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABDD2A522A2B00A000EE /* DispatchQueueSpy.swift */; }; 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */; }; 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */; }; 5EF1ACB52A4E47C6002EA972 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB32A4E47C6002EA972 /* Main.storyboard */; }; @@ -99,6 +101,8 @@ 5EEFABD42A52179A00A000EE /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; 5EEFABD62A52181C00A000EE /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extension.swift"; sourceTree = ""; }; + 5EEFABDA2A5226CC00A000EE /* DispatchQueue+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Extension.swift"; sourceTree = ""; }; + 5EEFABDD2A522A2B00A000EE /* DispatchQueueSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueueSpy.swift; sourceTree = ""; }; 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyBankApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -266,6 +270,7 @@ 5EEFABD42A52179A00A000EE /* Double+Extension.swift */, 5EEFABD62A52181C00A000EE /* String+Extension.swift */, 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */, + 5EEFABDA2A5226CC00A000EE /* DispatchQueue+Extension.swift */, ); path = Extensions; sourceTree = ""; @@ -299,6 +304,14 @@ path = Cells; sourceTree = ""; }; + 5EEFABDC2A522A1200A000EE /* Common */ = { + isa = PBXGroup; + children = ( + 5EEFABDD2A522A2B00A000EE /* DispatchQueueSpy.swift */, + ); + path = Common; + sourceTree = ""; + }; 5EF1ACA12A4E47C6002EA972 = { isa = PBXGroup; children = ( @@ -341,6 +354,7 @@ 5EF1ACC32A4E47C9002EA972 /* MyBankAppTests */ = { isa = PBXGroup; children = ( + 5EEFABDC2A522A1200A000EE /* Common */, 5EEFAB702A50B7F900A000EE /* Home */, 5EEFAB6D2A50B20A00A000EE /* Login */, 5EEFAB6A2A50B15700A000EE /* Network */, @@ -579,6 +593,7 @@ buildActionMask = 2147483647; files = ( 5EEFABBC2A51E6C800A000EE /* HomePresenter.swift in Sources */, + 5EEFABDB2A5226CC00A000EE /* DispatchQueue+Extension.swift in Sources */, 5EEFABBE2A51E71200A000EE /* HomeInteractor.swift in Sources */, 5EEFAB622A50A8E500A000EE /* HomeResponse.swift in Sources */, 5EEFAB5D2A50A88600A000EE /* NetworkServiceImpl.swift in Sources */, @@ -614,6 +629,7 @@ files = ( 5EEFAB6F2A50B21700A000EE /* LoginServiceTests.swift in Sources */, 5EEFAB722A50B80200A000EE /* HomeServiceTests.swift in Sources */, + 5EEFABDE2A522A2B00A000EE /* DispatchQueueSpy.swift in Sources */, 5EEFAB6C2A50B16E00A000EE /* NetworkServiceSpy.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/MyBankApp/MyBankApp/Extensions/DispatchQueue+Extension.swift b/MyBankApp/MyBankApp/Extensions/DispatchQueue+Extension.swift new file mode 100644 index 000000000..e97d4d66b --- /dev/null +++ b/MyBankApp/MyBankApp/Extensions/DispatchQueue+Extension.swift @@ -0,0 +1,11 @@ +import Foundation + +protocol DispatchQueueProtocol { + func async(execute work: @escaping () -> Void) +} + +class MainDispatchQueueWrapper: DispatchQueueProtocol { + func async(execute work: @escaping () -> Void) { + DispatchQueue.main.async(execute: work) + } +} diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeRouter.swift b/MyBankApp/MyBankApp/Home/Scene/HomeRouter.swift index 59e5535d5..42f4f8d57 100644 --- a/MyBankApp/MyBankApp/Home/Scene/HomeRouter.swift +++ b/MyBankApp/MyBankApp/Home/Scene/HomeRouter.swift @@ -7,7 +7,4 @@ final class HomeRouter: NSObject, HomeRoutingLogic, HomeDataPassing { func logout() { viewController?.navigationController?.popViewController(animated: true) } -// func routeToStatementDetail() { -// -// } } diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeViewController.swift b/MyBankApp/MyBankApp/Home/Scene/HomeViewController.swift index af215d94a..65caae9ba 100644 --- a/MyBankApp/MyBankApp/Home/Scene/HomeViewController.swift +++ b/MyBankApp/MyBankApp/Home/Scene/HomeViewController.swift @@ -13,9 +13,21 @@ final class HomeViewController: UIViewController { var interactor: HomeBusinessLogic? var router: (NSObjectProtocol & HomeRoutingLogic & HomeDataPassing)? + var dispatchQueue: DispatchQueueProtocol private var statements: [Statement] = [] + init(interactor: HomeBusinessLogic? = nil, router: HomeRouter? = nil, dispatchQueue: DispatchQueueProtocol = MainDispatchQueueWrapper()) { + self.interactor = interactor + self.router = router + self.dispatchQueue = dispatchQueue + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func viewDidLoad() { super.viewDidLoad() navigationController?.interactivePopGestureRecognizer?.isEnabled = false @@ -189,7 +201,7 @@ final class HomeViewController: UIViewController { // MARK: - HomeDisplayLogic Methods extension HomeViewController: HomeDisplayLogic { func displayStatements(_ statements: [Statement]) { - DispatchQueue.main.async { + dispatchQueue.async { self.statements = statements self.tableView.reloadData() } diff --git a/MyBankApp/MyBankApp/Home/Service/HomeService.swift b/MyBankApp/MyBankApp/Home/Service/HomeService.swift index 1b89c5cb2..f3b20d405 100644 --- a/MyBankApp/MyBankApp/Home/Service/HomeService.swift +++ b/MyBankApp/MyBankApp/Home/Service/HomeService.swift @@ -15,6 +15,7 @@ final class HomeService: HomeServiceProtocol { for id: String, completion: @escaping (Result) -> Void ) { + //id should be used at the end of the URL, but as the API is not working properly, i will not add the id at the end guard let url = URL(string: "https://6092aef785ff5100172136c2.mockapi.io/api/statements") else { completion(.failure(.invalidResponse)) return @@ -29,25 +30,3 @@ final class HomeService: HomeServiceProtocol { } } } - -/* - let homeService = HomeService() - - homeService.fetchStatements(for: "your-id") { result in - switch result { - case .success(let statements): - // Handle successful fetch - for statement in statements { - print("Statement ID:", statement.id) - print("Type:", statement.type) - print("Date:", statement.date) - print("Detail:", statement.detail) - print("Value:", statement.value) - print("--------") - } - case .failure(let error): - // Handle fetch error - print("Fetch error:", error.localizedDescription) - } - } - */ diff --git a/MyBankApp/MyBankAppTests/Common/DispatchQueueSpy.swift b/MyBankApp/MyBankAppTests/Common/DispatchQueueSpy.swift new file mode 100644 index 000000000..9a37f4f49 --- /dev/null +++ b/MyBankApp/MyBankAppTests/Common/DispatchQueueSpy.swift @@ -0,0 +1,9 @@ +import XCTest +@testable import MyBankApp + +typealias DispatchQueueDummy = DispatchQueueSpy +class DispatchQueueSpy: DispatchQueueProtocol { + func async(execute work: @escaping () -> Void) { + work() + } +} diff --git a/MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift b/MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift index fbcab8faa..208dc57ae 100644 --- a/MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift +++ b/MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift @@ -43,7 +43,7 @@ final class HomeServiceTests: XCTestCase { } // Then - XCTAssertEqual(networkServiceSpy.capturedURL?.absoluteString, "https://6092aef785ff5100172136c2.mockapi.io/api/statements/user123") + XCTAssertEqual(networkServiceSpy.capturedURL?.absoluteString, "https://6092aef785ff5100172136c2.mockapi.io/api/statements") XCTAssertEqual(networkServiceSpy.capturedMethod, .get) switch capturedResult { case .success(let statements): @@ -67,7 +67,7 @@ final class HomeServiceTests: XCTestCase { } // Then - XCTAssertEqual(networkServiceSpy.capturedURL?.absoluteString, "https://6092aef785ff5100172136c2.mockapi.io/api/statements/user123") + XCTAssertEqual(networkServiceSpy.capturedURL?.absoluteString, "https://6092aef785ff5100172136c2.mockapi.io/api/statements") XCTAssertEqual(networkServiceSpy.capturedMethod, .get) switch capturedResult { case .success(let statements): diff --git a/MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift b/MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift index a67d93c00..d7c02050f 100644 --- a/MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift +++ b/MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift @@ -20,8 +20,22 @@ final class LoginServiceTests: XCTestCase { func testLoginSuccess() { // Given let expectedID = "user123" + let expectedEmail = "fulano@teste.com" + let expectedCpf = "123.456.789-10" + let expectedName = "Fulano de Tail" + let expectedAccountNumber = "123456" + let expectedAgency = "123" + let expectedBalance = "R$ 1.000,00" let responseJSON = """ - {"id": "\(expectedID)"} + { + "userId": "\(expectedID)", + "email": "\(expectedEmail)", + "cpf": "\(expectedCpf)", + "name": "\(expectedName)", + "accountNumber": "\(expectedAccountNumber)", + "agency": "\(expectedAgency)", + "balance": "\(expectedBalance)" + } """ let responseData = responseJSON.data(using: .utf8)! networkServiceSpy.mockedResult = .success(responseData) @@ -37,7 +51,13 @@ final class LoginServiceTests: XCTestCase { XCTAssertEqual(networkServiceSpy.capturedMethod, .post) switch capturedResult { case let .success(response): - XCTAssertEqual(response.id, expectedID) + XCTAssertEqual(response.userId, expectedID) + XCTAssertEqual(response.email, expectedEmail) + XCTAssertEqual(response.cpf, expectedCpf) + XCTAssertEqual(response.name, expectedName) + XCTAssertEqual(response.accountNumber, expectedAccountNumber) + XCTAssertEqual(response.agency, expectedAgency) + XCTAssertEqual(response.balance, expectedBalance) case .failure(_): XCTFail() case .none: From eba0919527680a2a4e1763c5851882b557563da5 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Sun, 2 Jul 2023 22:54:25 -0300 Subject: [PATCH 13/27] =?UTF-8?q?LoginViewController=20tests=20e=20altera?= =?UTF-8?q?=C3=A7=C3=B5es=20referente=20ao=20teste=20da=20tela.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 20 +++++++ .../MyBankApp/Login/Scene/LoginRouter.swift | 2 +- .../Login/Scene/LoginViewController.swift | 30 +++++++--- .../Login/LoginViewControllerTests.swift | 56 +++++++++++++++++++ .../Login/Spies/LoginBusinessLogicSpy.swift | 14 +++++ .../Login/Spies/LoginRouterSpy.swift | 14 +++++ 6 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift create mode 100644 MyBankApp/MyBankAppTests/Login/Spies/LoginBusinessLogicSpy.swift create mode 100644 MyBankApp/MyBankAppTests/Login/Spies/LoginRouterSpy.swift diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index a939146d8..799e309bd 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -40,6 +40,9 @@ 5EEFABD92A52195600A000EE /* UIFont+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */; }; 5EEFABDB2A5226CC00A000EE /* DispatchQueue+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABDA2A5226CC00A000EE /* DispatchQueue+Extension.swift */; }; 5EEFABDE2A522A2B00A000EE /* DispatchQueueSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABDD2A522A2B00A000EE /* DispatchQueueSpy.swift */; }; + 5EEFABE02A522B5000A000EE /* LoginViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABDF2A522B5000A000EE /* LoginViewControllerTests.swift */; }; + 5EEFABE32A522C5400A000EE /* LoginBusinessLogicSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABE22A522C5400A000EE /* LoginBusinessLogicSpy.swift */; }; + 5EEFABE52A525A2100A000EE /* LoginRouterSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABE42A525A2100A000EE /* LoginRouterSpy.swift */; }; 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */; }; 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */; }; 5EF1ACB52A4E47C6002EA972 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB32A4E47C6002EA972 /* Main.storyboard */; }; @@ -103,6 +106,9 @@ 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extension.swift"; sourceTree = ""; }; 5EEFABDA2A5226CC00A000EE /* DispatchQueue+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Extension.swift"; sourceTree = ""; }; 5EEFABDD2A522A2B00A000EE /* DispatchQueueSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueueSpy.swift; sourceTree = ""; }; + 5EEFABDF2A522B5000A000EE /* LoginViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewControllerTests.swift; sourceTree = ""; }; + 5EEFABE22A522C5400A000EE /* LoginBusinessLogicSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginBusinessLogicSpy.swift; sourceTree = ""; }; + 5EEFABE42A525A2100A000EE /* LoginRouterSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRouterSpy.swift; sourceTree = ""; }; 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyBankApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -229,7 +235,9 @@ 5EEFAB6D2A50B20A00A000EE /* Login */ = { isa = PBXGroup; children = ( + 5EEFABE12A522C4900A000EE /* Spies */, 5EEFAB6E2A50B21700A000EE /* LoginServiceTests.swift */, + 5EEFABDF2A522B5000A000EE /* LoginViewControllerTests.swift */, ); path = Login; sourceTree = ""; @@ -312,6 +320,15 @@ path = Common; sourceTree = ""; }; + 5EEFABE12A522C4900A000EE /* Spies */ = { + isa = PBXGroup; + children = ( + 5EEFABE22A522C5400A000EE /* LoginBusinessLogicSpy.swift */, + 5EEFABE42A525A2100A000EE /* LoginRouterSpy.swift */, + ); + path = Spies; + sourceTree = ""; + }; 5EF1ACA12A4E47C6002EA972 = { isa = PBXGroup; children = ( @@ -627,9 +644,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5EEFABE52A525A2100A000EE /* LoginRouterSpy.swift in Sources */, 5EEFAB6F2A50B21700A000EE /* LoginServiceTests.swift in Sources */, 5EEFAB722A50B80200A000EE /* HomeServiceTests.swift in Sources */, 5EEFABDE2A522A2B00A000EE /* DispatchQueueSpy.swift in Sources */, + 5EEFABE32A522C5400A000EE /* LoginBusinessLogicSpy.swift in Sources */, + 5EEFABE02A522B5000A000EE /* LoginViewControllerTests.swift in Sources */, 5EEFAB6C2A50B16E00A000EE /* NetworkServiceSpy.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift b/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift index 3ee2167a0..6036a852f 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift @@ -1,6 +1,6 @@ import UIKit -final class LoginRouter: NSObject, LoginRoutingLogic, LoginDataPassing { +class LoginRouter: NSObject, LoginRoutingLogic, LoginDataPassing { weak var viewController: LoginViewController? var dataStore: LoginDataStore? diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift index 13f992200..4967a7174 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift @@ -13,7 +13,7 @@ final class LoginViewController: UIViewController { return imageView }() - private let usernameTextField: UITextField = { + let usernameTextField: UITextField = { let textField = UITextField() textField.placeholder = "Username" textField.borderStyle = .roundedRect @@ -21,7 +21,7 @@ final class LoginViewController: UIViewController { return textField }() - private let passwordTextField: UITextField = { + let passwordTextField: UITextField = { let textField = UITextField() textField.placeholder = "Password" textField.borderStyle = .roundedRect @@ -39,6 +39,19 @@ final class LoginViewController: UIViewController { return button }() + init( + interactor: LoginBusinessLogic? = nil, + router: LoginRouter? = nil + ) { + self.interactor = interactor + self.router = router + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + // MARK: View Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -90,11 +103,12 @@ final class LoginViewController: UIViewController { } // MARK: Actions - @objc private func loginButtonTapped() { + @objc func loginButtonTapped() { guard let username = usernameTextField.text, - let password = passwordTextField.text else { - return - } + let password = passwordTextField.text, !username.isEmpty, !password.isEmpty else { + displayLoginError(message: "Please fill user and password fields correctly!") + return + } interactor?.login(username: username, password: password) } @@ -106,6 +120,8 @@ extension LoginViewController: LoginDisplayLogic { } func displayLoginError(message: String) { - + let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alertController, animated: true, completion: nil) } } diff --git a/MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift b/MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift new file mode 100644 index 000000000..041981701 --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift @@ -0,0 +1,56 @@ +import XCTest +@testable import MyBankApp + +final class LoginViewControllerTests: XCTestCase { + + private var sut: LoginViewController! + private var interactorSpy: LoginBusinessLogicSpy! + private var routerSpy: LoginRouterSpy! + + override func setUp() { + super.setUp() + interactorSpy = LoginBusinessLogicSpy() + routerSpy = LoginRouterSpy() + sut = LoginViewController( + interactor: interactorSpy, + router: routerSpy + ) + } + + override func tearDown() { + interactorSpy = nil + routerSpy = nil + sut = nil + super.tearDown() + } + + func test_givenDisplayLoginSuccess_shouldRouteToHome() { + sut.displayLoginSuccess() + + XCTAssertEqual(routerSpy.calledMethods, [.routeToHome]) + } + + func test_givenUserNameAndPasswordAreFilled_shouldCallInteractorLogin() { + //Given + sut.usernameTextField.text = "test@test.com" + sut.passwordTextField.text = "T3st!ng" + + //When + sut.loginButtonTapped() + + //Then + XCTAssertEqual(interactorSpy.calledMethods, [.login(username: "test@test.com", password: "T3st!ng")]) + } + + func test_givenUserNameOrPasswordAreNotFilled_shouldNotCallInteractorLogin() { + //Given + sut.usernameTextField.text = "test@test.com" + sut.passwordTextField.text = "" + + //When + sut.loginButtonTapped() + + //Then + XCTAssertEqual(interactorSpy.calledMethods, []) + } +} diff --git a/MyBankApp/MyBankAppTests/Login/Spies/LoginBusinessLogicSpy.swift b/MyBankApp/MyBankAppTests/Login/Spies/LoginBusinessLogicSpy.swift new file mode 100644 index 000000000..5cb1ada74 --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/Spies/LoginBusinessLogicSpy.swift @@ -0,0 +1,14 @@ +import XCTest +@testable import MyBankApp + +final class LoginBusinessLogicSpy: LoginBusinessLogic { + enum Methods: Equatable { + case login(username: String, password: String) + } + + private(set) var calledMethods: [Methods] = [] + + func login(username: String, password: String) { + calledMethods.append(.login(username: username, password: password)) + } +} diff --git a/MyBankApp/MyBankAppTests/Login/Spies/LoginRouterSpy.swift b/MyBankApp/MyBankAppTests/Login/Spies/LoginRouterSpy.swift new file mode 100644 index 000000000..34f734123 --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/Spies/LoginRouterSpy.swift @@ -0,0 +1,14 @@ +import XCTest +@testable import MyBankApp + +final class LoginRouterSpy: LoginRouter { + enum Method: Equatable { + case routeToHome + } + + private(set) var calledMethods: [Method] = [] + + override func routeToHome() { + calledMethods.append(.routeToHome) + } +} From 8b7a45b65868fe5ac81324bcac1de363af12a3d2 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Sun, 2 Jul 2023 23:03:49 -0300 Subject: [PATCH 14/27] LoginPresenterTests criados e LoginDisplayLogicSpy adicionado. --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 8 +++++ .../Login/Scene/LoginPresenter.swift | 4 +++ .../Login/LoginPresenterTests.swift | 31 +++++++++++++++++++ .../Login/Spies/LoginDisplayLogicSpy.swift | 19 ++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 MyBankApp/MyBankAppTests/Login/LoginPresenterTests.swift create mode 100644 MyBankApp/MyBankAppTests/Login/Spies/LoginDisplayLogicSpy.swift diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index 799e309bd..ae8e7abcd 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -43,6 +43,8 @@ 5EEFABE02A522B5000A000EE /* LoginViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABDF2A522B5000A000EE /* LoginViewControllerTests.swift */; }; 5EEFABE32A522C5400A000EE /* LoginBusinessLogicSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABE22A522C5400A000EE /* LoginBusinessLogicSpy.swift */; }; 5EEFABE52A525A2100A000EE /* LoginRouterSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABE42A525A2100A000EE /* LoginRouterSpy.swift */; }; + 5EEFABE72A52620100A000EE /* LoginPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABE62A52620100A000EE /* LoginPresenterTests.swift */; }; + 5EEFABE92A52630E00A000EE /* LoginDisplayLogicSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABE82A52630E00A000EE /* LoginDisplayLogicSpy.swift */; }; 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */; }; 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */; }; 5EF1ACB52A4E47C6002EA972 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB32A4E47C6002EA972 /* Main.storyboard */; }; @@ -109,6 +111,8 @@ 5EEFABDF2A522B5000A000EE /* LoginViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewControllerTests.swift; sourceTree = ""; }; 5EEFABE22A522C5400A000EE /* LoginBusinessLogicSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginBusinessLogicSpy.swift; sourceTree = ""; }; 5EEFABE42A525A2100A000EE /* LoginRouterSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRouterSpy.swift; sourceTree = ""; }; + 5EEFABE62A52620100A000EE /* LoginPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresenterTests.swift; sourceTree = ""; }; + 5EEFABE82A52630E00A000EE /* LoginDisplayLogicSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginDisplayLogicSpy.swift; sourceTree = ""; }; 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyBankApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -238,6 +242,7 @@ 5EEFABE12A522C4900A000EE /* Spies */, 5EEFAB6E2A50B21700A000EE /* LoginServiceTests.swift */, 5EEFABDF2A522B5000A000EE /* LoginViewControllerTests.swift */, + 5EEFABE62A52620100A000EE /* LoginPresenterTests.swift */, ); path = Login; sourceTree = ""; @@ -325,6 +330,7 @@ children = ( 5EEFABE22A522C5400A000EE /* LoginBusinessLogicSpy.swift */, 5EEFABE42A525A2100A000EE /* LoginRouterSpy.swift */, + 5EEFABE82A52630E00A000EE /* LoginDisplayLogicSpy.swift */, ); path = Spies; sourceTree = ""; @@ -644,10 +650,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5EEFABE72A52620100A000EE /* LoginPresenterTests.swift in Sources */, 5EEFABE52A525A2100A000EE /* LoginRouterSpy.swift in Sources */, 5EEFAB6F2A50B21700A000EE /* LoginServiceTests.swift in Sources */, 5EEFAB722A50B80200A000EE /* HomeServiceTests.swift in Sources */, 5EEFABDE2A522A2B00A000EE /* DispatchQueueSpy.swift in Sources */, + 5EEFABE92A52630E00A000EE /* LoginDisplayLogicSpy.swift in Sources */, 5EEFABE32A522C5400A000EE /* LoginBusinessLogicSpy.swift in Sources */, 5EEFABE02A522B5000A000EE /* LoginViewControllerTests.swift in Sources */, 5EEFAB6C2A50B16E00A000EE /* NetworkServiceSpy.swift in Sources */, diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift b/MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift index 4a455aff3..a7d4b95cc 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginPresenter.swift @@ -3,6 +3,10 @@ import Foundation final class LoginPresenter: LoginPresentationLogic { weak var viewController: LoginDisplayLogic? + init(viewController: LoginDisplayLogic? = nil) { + self.viewController = viewController + } + func presentLoginSuccess() { viewController?.displayLoginSuccess() } diff --git a/MyBankApp/MyBankAppTests/Login/LoginPresenterTests.swift b/MyBankApp/MyBankAppTests/Login/LoginPresenterTests.swift new file mode 100644 index 000000000..2ca226d91 --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/LoginPresenterTests.swift @@ -0,0 +1,31 @@ +import XCTest +@testable import MyBankApp + +final class LoginPresenterTests: XCTestCase { + private var sut: LoginPresenter! + private var viewControllerSpy: LoginDisplayLogicSpy! + + override func setUp() { + super.setUp() + viewControllerSpy = LoginDisplayLogicSpy() + sut = LoginPresenter(viewController: viewControllerSpy) + } + + override func tearDown() { + viewControllerSpy = nil + sut = nil + super.tearDown() + } + + func test_presentLoginSuccess_shouldCallViewControllerpresentLoginSuccess() { + sut.presentLoginSuccess() + + XCTAssertEqual(viewControllerSpy.calledMethods, [.displayLoginSuccess]) + } + + func test_presentLoginError_shouldCallViewControllerpresentLoginErrorWithMessage() { + sut.presentLoginError(message: "message") + + XCTAssertEqual(viewControllerSpy.calledMethods, [.displayLoginError(message: "message")]) + } +} diff --git a/MyBankApp/MyBankAppTests/Login/Spies/LoginDisplayLogicSpy.swift b/MyBankApp/MyBankAppTests/Login/Spies/LoginDisplayLogicSpy.swift new file mode 100644 index 000000000..7894811ba --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/Spies/LoginDisplayLogicSpy.swift @@ -0,0 +1,19 @@ +import XCTest +@testable import MyBankApp + +final class LoginDisplayLogicSpy: LoginDisplayLogic { + enum Method: Equatable { + case displayLoginSuccess + case displayLoginError(message: String) + } + + private(set) var calledMethods: [Method] = [] + + func displayLoginSuccess() { + calledMethods.append(.displayLoginSuccess) + } + + func displayLoginError(message: String) { + calledMethods.append(.displayLoginError(message: message)) + } +} From bffcdbfd9cc990b027bc0350220fafad6124d4f1 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Sun, 2 Jul 2023 23:38:50 -0300 Subject: [PATCH 15/27] LoginInteractorTests, Spies e fixtures referente ao teste. --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 24 +++++++++++ .../Login/Scene/LoginInteractor.swift | 31 +++++++------- .../Fixtures/LoginResponse+Fixture.swift | 23 +++++++++++ .../Login/LoginInteractorTests.swift | 40 +++++++++++++++++++ .../Spies/LoginPresentationLogicSpy.swift | 18 +++++++++ .../Login/Spies/LoginServiceSpy.swift | 15 +++++++ 6 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 MyBankApp/MyBankAppTests/Login/Fixtures/LoginResponse+Fixture.swift create mode 100644 MyBankApp/MyBankAppTests/Login/LoginInteractorTests.swift create mode 100644 MyBankApp/MyBankAppTests/Login/Spies/LoginPresentationLogicSpy.swift create mode 100644 MyBankApp/MyBankAppTests/Login/Spies/LoginServiceSpy.swift diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index ae8e7abcd..aeb9a00c7 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -45,6 +45,10 @@ 5EEFABE52A525A2100A000EE /* LoginRouterSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABE42A525A2100A000EE /* LoginRouterSpy.swift */; }; 5EEFABE72A52620100A000EE /* LoginPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABE62A52620100A000EE /* LoginPresenterTests.swift */; }; 5EEFABE92A52630E00A000EE /* LoginDisplayLogicSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABE82A52630E00A000EE /* LoginDisplayLogicSpy.swift */; }; + 5EEFABEB2A5265C700A000EE /* LoginInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABEA2A5265C700A000EE /* LoginInteractorTests.swift */; }; + 5EEFABED2A52667500A000EE /* LoginServiceSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABEC2A52667500A000EE /* LoginServiceSpy.swift */; }; + 5EEFABF02A52671E00A000EE /* LoginResponse+Fixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABEF2A52671E00A000EE /* LoginResponse+Fixture.swift */; }; + 5EEFABF22A5267E500A000EE /* LoginPresentationLogicSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABF12A5267E500A000EE /* LoginPresentationLogicSpy.swift */; }; 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */; }; 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */; }; 5EF1ACB52A4E47C6002EA972 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB32A4E47C6002EA972 /* Main.storyboard */; }; @@ -113,6 +117,10 @@ 5EEFABE42A525A2100A000EE /* LoginRouterSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRouterSpy.swift; sourceTree = ""; }; 5EEFABE62A52620100A000EE /* LoginPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresenterTests.swift; sourceTree = ""; }; 5EEFABE82A52630E00A000EE /* LoginDisplayLogicSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginDisplayLogicSpy.swift; sourceTree = ""; }; + 5EEFABEA2A5265C700A000EE /* LoginInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInteractorTests.swift; sourceTree = ""; }; + 5EEFABEC2A52667500A000EE /* LoginServiceSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginServiceSpy.swift; sourceTree = ""; }; + 5EEFABEF2A52671E00A000EE /* LoginResponse+Fixture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginResponse+Fixture.swift"; sourceTree = ""; }; + 5EEFABF12A5267E500A000EE /* LoginPresentationLogicSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresentationLogicSpy.swift; sourceTree = ""; }; 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyBankApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -239,10 +247,12 @@ 5EEFAB6D2A50B20A00A000EE /* Login */ = { isa = PBXGroup; children = ( + 5EEFABEE2A52671300A000EE /* Fixtures */, 5EEFABE12A522C4900A000EE /* Spies */, 5EEFAB6E2A50B21700A000EE /* LoginServiceTests.swift */, 5EEFABDF2A522B5000A000EE /* LoginViewControllerTests.swift */, 5EEFABE62A52620100A000EE /* LoginPresenterTests.swift */, + 5EEFABEA2A5265C700A000EE /* LoginInteractorTests.swift */, ); path = Login; sourceTree = ""; @@ -331,10 +341,20 @@ 5EEFABE22A522C5400A000EE /* LoginBusinessLogicSpy.swift */, 5EEFABE42A525A2100A000EE /* LoginRouterSpy.swift */, 5EEFABE82A52630E00A000EE /* LoginDisplayLogicSpy.swift */, + 5EEFABEC2A52667500A000EE /* LoginServiceSpy.swift */, + 5EEFABF12A5267E500A000EE /* LoginPresentationLogicSpy.swift */, ); path = Spies; sourceTree = ""; }; + 5EEFABEE2A52671300A000EE /* Fixtures */ = { + isa = PBXGroup; + children = ( + 5EEFABEF2A52671E00A000EE /* LoginResponse+Fixture.swift */, + ); + path = Fixtures; + sourceTree = ""; + }; 5EF1ACA12A4E47C6002EA972 = { isa = PBXGroup; children = ( @@ -654,11 +674,15 @@ 5EEFABE52A525A2100A000EE /* LoginRouterSpy.swift in Sources */, 5EEFAB6F2A50B21700A000EE /* LoginServiceTests.swift in Sources */, 5EEFAB722A50B80200A000EE /* HomeServiceTests.swift in Sources */, + 5EEFABF22A5267E500A000EE /* LoginPresentationLogicSpy.swift in Sources */, 5EEFABDE2A522A2B00A000EE /* DispatchQueueSpy.swift in Sources */, 5EEFABE92A52630E00A000EE /* LoginDisplayLogicSpy.swift in Sources */, 5EEFABE32A522C5400A000EE /* LoginBusinessLogicSpy.swift in Sources */, 5EEFABE02A522B5000A000EE /* LoginViewControllerTests.swift in Sources */, + 5EEFABF02A52671E00A000EE /* LoginResponse+Fixture.swift in Sources */, + 5EEFABEB2A5265C700A000EE /* LoginInteractorTests.swift in Sources */, 5EEFAB6C2A50B16E00A000EE /* NetworkServiceSpy.swift in Sources */, + 5EEFABED2A52667500A000EE /* LoginServiceSpy.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift index 43702737d..3cb3ac99b 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift @@ -6,24 +6,27 @@ final class LoginInteractor: LoginBusinessLogic, LoginDataStore { var user: LoginResponse? - init(presenter: LoginPresentationLogic? = nil, loginService: LoginServiceProtocol = LoginService()) { + init( + presenter: LoginPresentationLogic? = nil, + loginService: LoginServiceProtocol = LoginService() + ) { self.presenter = presenter self.loginService = loginService } func login(username: String, password: String) { - presenter?.presentLoginSuccess() -// loginService.login(user: username, password: password) { [weak self, presenter] result in -// switch result { -// case .success(let response): -// guard let self = self else { return } -// print("Logged in with ID:", response.userId) -// user = response -// presenter?.presentLoginSuccess() -// case .failure(let error): -// print("Login error:", error.localizedDescription) -// presenter?.presentLoginError(message: "Invalid credentials") -// } -// } + //presenter?.presentLoginSuccess() ///Uncomment this line, and comment the loginService implementation if Login API is not working + loginService.login(user: username, password: password) { [weak self, presenter] result in + switch result { + case .success(let response): + guard let self = self else { return } + print("Logged in with ID:", response.userId) + user = response + presenter?.presentLoginSuccess() + case .failure(let error): + print("Login error:", error.localizedDescription) + presenter?.presentLoginError(message: "Invalid credentials") + } + } } } diff --git a/MyBankApp/MyBankAppTests/Login/Fixtures/LoginResponse+Fixture.swift b/MyBankApp/MyBankAppTests/Login/Fixtures/LoginResponse+Fixture.swift new file mode 100644 index 000000000..69756f72f --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/Fixtures/LoginResponse+Fixture.swift @@ -0,0 +1,23 @@ +@testable import MyBankApp + +extension LoginResponse { + static func fixture( + userId: String = "1", + email: String = "test@test.com", + cpf: String = "123.456.789-10", + name: String = "Test", + accountNumber: String = "123456", + agency: String = "123", + balance: String = "R$ 1.000,00" + ) -> LoginResponse { + .init( + userId: userId, + email: email, + cpf: cpf, + name: name, + accountNumber: accountNumber, + agency: agency, + balance: balance + ) + } +} diff --git a/MyBankApp/MyBankAppTests/Login/LoginInteractorTests.swift b/MyBankApp/MyBankAppTests/Login/LoginInteractorTests.swift new file mode 100644 index 000000000..f41100ed0 --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/LoginInteractorTests.swift @@ -0,0 +1,40 @@ +import XCTest +@testable import MyBankApp + +final class LoginInteractorTests: XCTestCase { + private var sut: LoginInteractor! + private var presenterSpy: LoginPresentationLogicSpy! + private var loginServiceSpy: LoginServiceSpy! + + override func setUp() { + super.setUp() + presenterSpy = LoginPresentationLogicSpy() + loginServiceSpy = LoginServiceSpy() + sut = LoginInteractor(presenter: presenterSpy, loginService: loginServiceSpy) + } + + override func tearDown() { + presenterSpy = nil + loginServiceSpy = nil + sut = nil + super.tearDown() + } + + func test_login_givenLoginSuccess_shouldCallPresentLoginSuccess() { + loginServiceSpy.completionToBeReturned = .success(.fixture()) + + sut.login(username: "", password: "") + + XCTAssertNotNil(sut.user) + XCTAssertEqual(presenterSpy.calledMethods, [.presentLoginSuccess]) + } + + func test_login_givenLoginFailure_shouldCallPresentLoginError() { + loginServiceSpy.completionToBeReturned = .failure(.requestFailed) + + sut.login(username: "", password: "") + + XCTAssertNil(sut.user) + XCTAssertEqual(presenterSpy.calledMethods, [.presentLoginError(message: "Invalid credentials")]) + } +} diff --git a/MyBankApp/MyBankAppTests/Login/Spies/LoginPresentationLogicSpy.swift b/MyBankApp/MyBankAppTests/Login/Spies/LoginPresentationLogicSpy.swift new file mode 100644 index 000000000..04ae33aba --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/Spies/LoginPresentationLogicSpy.swift @@ -0,0 +1,18 @@ +import XCTest +@testable import MyBankApp + +final class LoginPresentationLogicSpy: LoginPresentationLogic { + enum Method: Equatable { + case presentLoginSuccess + case presentLoginError(message: String) + } + private(set) var calledMethods: [Method] = [] + + func presentLoginSuccess() { + calledMethods.append(.presentLoginSuccess) + } + + func presentLoginError(message: String) { + calledMethods.append(.presentLoginError(message: message)) + } +} diff --git a/MyBankApp/MyBankAppTests/Login/Spies/LoginServiceSpy.swift b/MyBankApp/MyBankAppTests/Login/Spies/LoginServiceSpy.swift new file mode 100644 index 000000000..08680a88f --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/Spies/LoginServiceSpy.swift @@ -0,0 +1,15 @@ +import XCTest +@testable import MyBankApp + +final class LoginServiceSpy: LoginServiceProtocol { + enum Method: Equatable { + case login(user: String, password: String) + } + private(set) var calledMethods: [Method] = [] + + var completionToBeReturned: Result = .success(.fixture()) + func login(user: String, password: String, completion: @escaping (Result) -> Void) { + calledMethods.append(.login(user: user, password: password)) + completion(completionToBeReturned) + } +} From 6988105a70b65a207a8e84c6186a3bbd89c6e67e Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Mon, 3 Jul 2023 02:14:26 -0300 Subject: [PATCH 16/27] =?UTF-8?q?LoginRouter=20e=20LoginConfiguratorTests.?= =?UTF-8?q?=20NavigationSpy=20adicionada.=20Pequenas=20altera=C3=A7=C3=B5e?= =?UTF-8?q?s=20necess=C3=A1rias=20para=20os=20testes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 20 +++ .../Extensions/Mirror+Extension.swift | 9 ++ .../Home/Scene/HomeConfigurator.swift | 2 +- .../Login/Models/LoginResponse.swift | 2 +- .../Login/Scene/LoginConfigurator.swift | 6 +- .../MyBankApp/Login/Scene/LoginRouter.swift | 2 +- .../Login/Scene/LoginViewController.swift | 2 +- .../Common/NavigationControllerSpy.swift | 116 ++++++++++++++++++ .../Login/LoginConfiguratorTests.swift | 62 ++++++++++ .../Login/LoginRouterTests.swift | 30 +++++ .../Login/Spies/LoginViewControllerSpy.swift | 10 ++ 11 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 MyBankApp/MyBankApp/Extensions/Mirror+Extension.swift create mode 100644 MyBankApp/MyBankAppTests/Common/NavigationControllerSpy.swift create mode 100644 MyBankApp/MyBankAppTests/Login/LoginConfiguratorTests.swift create mode 100644 MyBankApp/MyBankAppTests/Login/LoginRouterTests.swift create mode 100644 MyBankApp/MyBankAppTests/Login/Spies/LoginViewControllerSpy.swift diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index aeb9a00c7..4ce7640f8 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -49,6 +49,11 @@ 5EEFABED2A52667500A000EE /* LoginServiceSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABEC2A52667500A000EE /* LoginServiceSpy.swift */; }; 5EEFABF02A52671E00A000EE /* LoginResponse+Fixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABEF2A52671E00A000EE /* LoginResponse+Fixture.swift */; }; 5EEFABF22A5267E500A000EE /* LoginPresentationLogicSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABF12A5267E500A000EE /* LoginPresentationLogicSpy.swift */; }; + 5EEFABF82A526D2900A000EE /* LoginRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABF72A526D2900A000EE /* LoginRouterTests.swift */; }; + 5EEFABFA2A526D9300A000EE /* NavigationControllerSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABF92A526D9300A000EE /* NavigationControllerSpy.swift */; }; + 5EEFABFC2A52770E00A000EE /* LoginViewControllerSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABFB2A52770E00A000EE /* LoginViewControllerSpy.swift */; }; + 5EEFABFE2A52836900A000EE /* LoginConfiguratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABFD2A52836900A000EE /* LoginConfiguratorTests.swift */; }; + 5EEFAC002A52854E00A000EE /* Mirror+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABFF2A52854E00A000EE /* Mirror+Extension.swift */; }; 5EF1ACAE2A4E47C6002EA972 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */; }; 5EF1ACB02A4E47C6002EA972 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */; }; 5EF1ACB52A4E47C6002EA972 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1ACB32A4E47C6002EA972 /* Main.storyboard */; }; @@ -121,6 +126,11 @@ 5EEFABEC2A52667500A000EE /* LoginServiceSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginServiceSpy.swift; sourceTree = ""; }; 5EEFABEF2A52671E00A000EE /* LoginResponse+Fixture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginResponse+Fixture.swift"; sourceTree = ""; }; 5EEFABF12A5267E500A000EE /* LoginPresentationLogicSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresentationLogicSpy.swift; sourceTree = ""; }; + 5EEFABF72A526D2900A000EE /* LoginRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRouterTests.swift; sourceTree = ""; }; + 5EEFABF92A526D9300A000EE /* NavigationControllerSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationControllerSpy.swift; sourceTree = ""; }; + 5EEFABFB2A52770E00A000EE /* LoginViewControllerSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewControllerSpy.swift; sourceTree = ""; }; + 5EEFABFD2A52836900A000EE /* LoginConfiguratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginConfiguratorTests.swift; sourceTree = ""; }; + 5EEFABFF2A52854E00A000EE /* Mirror+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mirror+Extension.swift"; sourceTree = ""; }; 5EF1ACAA2A4E47C6002EA972 /* MyBankApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyBankApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5EF1ACAD2A4E47C6002EA972 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5EF1ACAF2A4E47C6002EA972 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -253,6 +263,8 @@ 5EEFABDF2A522B5000A000EE /* LoginViewControllerTests.swift */, 5EEFABE62A52620100A000EE /* LoginPresenterTests.swift */, 5EEFABEA2A5265C700A000EE /* LoginInteractorTests.swift */, + 5EEFABF72A526D2900A000EE /* LoginRouterTests.swift */, + 5EEFABFD2A52836900A000EE /* LoginConfiguratorTests.swift */, ); path = Login; sourceTree = ""; @@ -294,6 +306,7 @@ 5EEFABD62A52181C00A000EE /* String+Extension.swift */, 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */, 5EEFABDA2A5226CC00A000EE /* DispatchQueue+Extension.swift */, + 5EEFABFF2A52854E00A000EE /* Mirror+Extension.swift */, ); path = Extensions; sourceTree = ""; @@ -331,6 +344,7 @@ isa = PBXGroup; children = ( 5EEFABDD2A522A2B00A000EE /* DispatchQueueSpy.swift */, + 5EEFABF92A526D9300A000EE /* NavigationControllerSpy.swift */, ); path = Common; sourceTree = ""; @@ -343,6 +357,7 @@ 5EEFABE82A52630E00A000EE /* LoginDisplayLogicSpy.swift */, 5EEFABEC2A52667500A000EE /* LoginServiceSpy.swift */, 5EEFABF12A5267E500A000EE /* LoginPresentationLogicSpy.swift */, + 5EEFABFB2A52770E00A000EE /* LoginViewControllerSpy.swift */, ); path = Spies; sourceTree = ""; @@ -645,6 +660,7 @@ 5EEFAB922A50D04B00A000EE /* LoginInteractor.swift in Sources */, 5EEFABB42A51E56D00A000EE /* UIColor+Extension.swift in Sources */, 5EEFABC72A52133B00A000EE /* StatementTableViewCell.swift in Sources */, + 5EEFAC002A52854E00A000EE /* Mirror+Extension.swift in Sources */, 5EEFAB4E2A50982B00A000EE /* NetworkProtocols.swift in Sources */, 5EEFAB802A50CB8500A000EE /* LoginViewController.swift in Sources */, 5EEFABD72A52181C00A000EE /* String+Extension.swift in Sources */, @@ -670,15 +686,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5EEFABFE2A52836900A000EE /* LoginConfiguratorTests.swift in Sources */, 5EEFABE72A52620100A000EE /* LoginPresenterTests.swift in Sources */, 5EEFABE52A525A2100A000EE /* LoginRouterSpy.swift in Sources */, 5EEFAB6F2A50B21700A000EE /* LoginServiceTests.swift in Sources */, 5EEFAB722A50B80200A000EE /* HomeServiceTests.swift in Sources */, 5EEFABF22A5267E500A000EE /* LoginPresentationLogicSpy.swift in Sources */, 5EEFABDE2A522A2B00A000EE /* DispatchQueueSpy.swift in Sources */, + 5EEFABFA2A526D9300A000EE /* NavigationControllerSpy.swift in Sources */, + 5EEFABF82A526D2900A000EE /* LoginRouterTests.swift in Sources */, 5EEFABE92A52630E00A000EE /* LoginDisplayLogicSpy.swift in Sources */, 5EEFABE32A522C5400A000EE /* LoginBusinessLogicSpy.swift in Sources */, 5EEFABE02A522B5000A000EE /* LoginViewControllerTests.swift in Sources */, + 5EEFABFC2A52770E00A000EE /* LoginViewControllerSpy.swift in Sources */, 5EEFABF02A52671E00A000EE /* LoginResponse+Fixture.swift in Sources */, 5EEFABEB2A5265C700A000EE /* LoginInteractorTests.swift in Sources */, 5EEFAB6C2A50B16E00A000EE /* NetworkServiceSpy.swift in Sources */, diff --git a/MyBankApp/MyBankApp/Extensions/Mirror+Extension.swift b/MyBankApp/MyBankApp/Extensions/Mirror+Extension.swift new file mode 100644 index 000000000..261fd13bf --- /dev/null +++ b/MyBankApp/MyBankApp/Extensions/Mirror+Extension.swift @@ -0,0 +1,9 @@ +extension Mirror { + func firstChild(of type: T.Type, in label: String? = nil) -> T? { + return children.lazy.compactMap { + guard let value = $0.value as? T else { return nil } + guard let label = label else { return value } + return $0.label == label ? value : nil + }.first + } +} diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift b/MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift index cf95489c6..c476edfd8 100644 --- a/MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift +++ b/MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift @@ -1,4 +1,4 @@ -import Foundation +import UIKit final class HomeConfigurator { func setup() -> HomeViewController { diff --git a/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift b/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift index 90ca57f08..a32de144f 100644 --- a/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift +++ b/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift @@ -1,6 +1,6 @@ import Foundation -struct LoginResponse: Decodable { +struct LoginResponse: Decodable, Equatable { let userId: String let email: String let cpf: String diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift b/MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift index 1c1b1a471..08eca675c 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginConfigurator.swift @@ -1,4 +1,4 @@ -import Foundation +import UIKit final class LoginConfigurator { func setup() -> LoginViewController { @@ -6,12 +6,16 @@ final class LoginConfigurator { let interactor = LoginInteractor() let presenter = LoginPresenter() let router = LoginRouter() + viewController.interactor = interactor viewController.router = router + interactor.presenter = presenter presenter.viewController = viewController + router.viewController = viewController router.dataStore = interactor + return viewController } } diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift b/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift index 6036a852f..bf83a911b 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginRouter.swift @@ -16,7 +16,7 @@ class LoginRouter: NSObject, LoginRoutingLogic, LoginDataPassing { agency: "0", balance: "R$ 1.000,00" ) - homeDataStore?.user = mockUserResponse//dataStore?.user + homeDataStore?.user = mockUserResponse//dataStore?.user /// Uncomment this line if login API comes back to work homeViewController.navigationItem.setHidesBackButton(true, animated: false) viewController?.navigationController?.pushViewController(homeViewController, animated: true) } diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift index 4967a7174..f5af5c700 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift @@ -1,6 +1,6 @@ import UIKit -final class LoginViewController: UIViewController { +class LoginViewController: UIViewController { var interactor: LoginBusinessLogic? var router: (NSObjectProtocol & LoginRoutingLogic & LoginDataPassing)? diff --git a/MyBankApp/MyBankAppTests/Common/NavigationControllerSpy.swift b/MyBankApp/MyBankAppTests/Common/NavigationControllerSpy.swift new file mode 100644 index 000000000..b39eaed10 --- /dev/null +++ b/MyBankApp/MyBankAppTests/Common/NavigationControllerSpy.swift @@ -0,0 +1,116 @@ +import UIKit + +final class NavigationControllerSpy: UINavigationController { + enum Methods: Equatable { + case show(UIViewController, Any?) + case presentViewController(UIViewController, animated: Bool) + case popToViewController(UIViewController, animated: Bool) + case pushViewController(UIViewController, animated: Bool) + case popToRootViewController(animated: Bool) + case popViewController(animated: Bool) + case dismiss(animated: Bool) + case setViewControllers([UIViewController], animated: Bool) + // MARK: View Type Helpers + case presentViewControllerType(UIViewController.Type, animated: Bool) + case popToViewControllerType(UIViewController.Type, animated: Bool) + case pushViewControllerType(UIViewController.Type, animated: Bool) + } + + private(set) var calledMethods: [Methods] = [] + private func called(_ method: Methods) { + calledMethods.append(method) + } + + public init() { + super.init(nibName: nil, bundle: nil) + } + + public override init(rootViewController: UIViewController) { + super.init(rootViewController: rootViewController) + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + nil + } + + // MARK: - pushViewController + public override func pushViewController(_ viewController: UIViewController, animated: Bool) { + called(.pushViewController(viewController, animated: animated)) + super.pushViewController(viewController, animated: animated) + } + + // MARK: - popToRootViewController + public var popToRootViewControllerViewControllersToBeReturned: [UIViewController]? + public override func popToRootViewController(animated: Bool) -> [UIViewController]? { + called(.popToRootViewController(animated: animated)) + return popToRootViewControllerViewControllersToBeReturned + } + + // MARK: - setViewControllers + public var setViewControllersToBeReturned: [UIViewController]? + public override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { + super.setViewControllers(viewControllers, animated: animated) + called(.setViewControllers(viewControllers, animated: animated)) + setViewControllersToBeReturned = viewControllers + } + + // MARK: - popViewController + public var popViewControllerToBeReturned: UIViewController? + public override func popViewController(animated: Bool) -> UIViewController? { + called(.popViewController(animated: animated)) + return popViewControllerToBeReturned + } + + // MARK: - show + public override func show(_ vc: UIViewController, sender: Any?) { + called(.show(vc, sender)) + } + + // MARK: - popToViewController + public var popToViewControllersToBeReturned: [UIViewController]? + public override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? { + called(.popToViewController(viewController, animated: animated)) + return popToViewControllersToBeReturned + } + + // MARK: - present + public override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + called(.presentViewController(viewControllerToPresent, animated: flag)) + completion?() + } + + // MARK: - dismiss + public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + called(.dismiss(animated: flag)) + completion?() + } +} + +extension NavigationControllerSpy.Methods { + static func == (_ lhs: Self, _ rhs: Self) -> Bool { + switch (lhs, rhs) { + case let (.presentViewControllerType(vcType, lhsA), .presentViewController(vc, rhsA)), + let (.popToViewControllerType(vcType, lhsA), .popToViewController(vc, rhsA)), + let (.pushViewControllerType(vcType, lhsA), .pushViewController(vc, rhsA)), + let (.presentViewController(vc, lhsA), .presentViewControllerType(vcType, rhsA)), + let (.popToViewController(vc, lhsA), .popToViewControllerType(vcType, rhsA)), + let (.pushViewController(vc, lhsA), .pushViewControllerType(vcType, rhsA)): + return lhsA == rhsA && String(describing: vc.classForCoder) == String(describing: vcType) + case let (.presentViewControllerType(lhsT, lhsA), .presentViewControllerType(rhsT, rhsA)), + let (.popToViewControllerType(lhsT, lhsA), .popToViewControllerType(rhsT, rhsA)), + let (.pushViewControllerType(lhsT, lhsA), .pushViewControllerType(rhsT, rhsA)): + return lhsA == rhsA && String(describing: lhsT) == String(describing: rhsT) + case let (.presentViewController(lhsVC, lhsA), .presentViewController(rhsVC, rhsA)), + let (.popToViewController(lhsVC, lhsA), .popToViewController(rhsVC, rhsA)), + let (.pushViewController(lhsVC, lhsA), .pushViewController(rhsVC, rhsA)): + return lhsVC == rhsVC && lhsA == rhsA + case let (.popToRootViewController(lhs), .popToRootViewController(rhs)), + let (.popViewController(lhs), .popViewController(rhs)), + let (.dismiss(lhs), .dismiss(rhs)): + return lhs == rhs + default: + return false + } + } +} diff --git a/MyBankApp/MyBankAppTests/Login/LoginConfiguratorTests.swift b/MyBankApp/MyBankAppTests/Login/LoginConfiguratorTests.swift new file mode 100644 index 000000000..a431289ca --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/LoginConfiguratorTests.swift @@ -0,0 +1,62 @@ +import XCTest +@testable import MyBankApp + +final class LoginConfiguratorTests: XCTestCase { + + private var sut: LoginConfigurator! + + override func setUp() { + super.setUp() + sut = LoginConfigurator() + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + func test_setup() { + // When + let viewController = sut.setup() + + // Then + XCTAssertNotNil(viewController) + + let interactor = Mirror(reflecting: viewController).firstChild(of: LoginInteractor.self, in: "interactor") + XCTAssertNotNil(interactor) + XCTAssertTrue(interactor?.presenter is LoginPresenter) + + let presenter = Mirror(reflecting: interactor!).firstChild(of: LoginPresenter.self, in: "presenter") + XCTAssertNotNil(presenter) + XCTAssertTrue(presenter?.viewController === viewController) + + let router = Mirror(reflecting: viewController).firstChild(of: LoginRouter.self, in: "router") + XCTAssertNotNil(router) + XCTAssertTrue(router?.viewController === viewController) + XCTAssertTrue(router?.dataStore as? LoginInteractor === interactor) + } + +/// * +/// This next test is another approach for the same purpose +/// * +/// func test_setup2() { +/// // When +/// let viewController = sut.setup() +/// +/// // Then +/// XCTAssertNotNil(viewController) +/// +/// let interactor = viewController.interactor as? LoginInteractor +/// XCTAssertNotNil(interactor) +/// XCTAssertTrue(interactor?.presenter is LoginPresenter) +/// +/// let presenter = interactor?.presenter as? LoginPresenter +/// XCTAssertNotNil(presenter) +/// XCTAssertTrue(presenter?.viewController === viewController) +/// +/// let router = viewController.router as? LoginRouter +/// XCTAssertNotNil(router) +/// XCTAssertTrue(router?.viewController === viewController) +/// } +} + diff --git a/MyBankApp/MyBankAppTests/Login/LoginRouterTests.swift b/MyBankApp/MyBankAppTests/Login/LoginRouterTests.swift new file mode 100644 index 000000000..898091dce --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/LoginRouterTests.swift @@ -0,0 +1,30 @@ +import XCTest +@testable import MyBankApp + +final class LoginRouterTests: XCTestCase { + private var sut: LoginRouter! + var viewController: LoginViewControllerSpy! + + override func setUp() { + super.setUp() + sut = LoginRouter() + viewController = LoginViewControllerSpy() + sut.viewController = viewController + } + + override func tearDown() { + viewController = nil + sut = nil + super.tearDown() + } + + func test_routeToHome_viewControllerShouldRouteToHomeViewController() { + let navigationControllerSpy = NavigationControllerSpy() + viewController.navigationControllerSpy = navigationControllerSpy + + sut.routeToHome() + + XCTAssertEqual(navigationControllerSpy.calledMethods, [.pushViewControllerType(HomeViewController.self, animated: true)]) + } + +} diff --git a/MyBankApp/MyBankAppTests/Login/Spies/LoginViewControllerSpy.swift b/MyBankApp/MyBankAppTests/Login/Spies/LoginViewControllerSpy.swift new file mode 100644 index 000000000..ec0ed7d26 --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/Spies/LoginViewControllerSpy.swift @@ -0,0 +1,10 @@ +import XCTest +@testable import MyBankApp + +final class LoginViewControllerSpy: LoginViewController { + var navigationControllerSpy: NavigationControllerSpy? + + override var navigationController: UINavigationController? { + return navigationControllerSpy + } +} From 87505f024c9098f6b348464ae021ecd2f0c54df2 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Mon, 3 Jul 2023 02:23:36 -0300 Subject: [PATCH 17/27] Adicionando Snapshot da LoginViewController. --- .../Login/LoginViewControllerTests.swift | 8 ++++++++ .../test_viewDidLoad_shouldMatchSnapshot.1.png | Bin 0 -> 95141 bytes 2 files changed, 8 insertions(+) create mode 100644 MyBankApp/MyBankAppTests/Login/__Snapshots__/LoginViewControllerTests/test_viewDidLoad_shouldMatchSnapshot.1.png diff --git a/MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift b/MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift index 041981701..0ed1dc565 100644 --- a/MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift +++ b/MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift @@ -1,4 +1,5 @@ import XCTest +import SnapshotTesting @testable import MyBankApp final class LoginViewControllerTests: XCTestCase { @@ -15,6 +16,7 @@ final class LoginViewControllerTests: XCTestCase { interactor: interactorSpy, router: routerSpy ) + isRecording = false } override func tearDown() { @@ -23,6 +25,12 @@ final class LoginViewControllerTests: XCTestCase { sut = nil super.tearDown() } + + func test_viewDidLoad_shouldMatchSnapshot() { + sut.viewDidLoad() + + assertSnapshot(matching: sut, as: .image) + } func test_givenDisplayLoginSuccess_shouldRouteToHome() { sut.displayLoginSuccess() diff --git a/MyBankApp/MyBankAppTests/Login/__Snapshots__/LoginViewControllerTests/test_viewDidLoad_shouldMatchSnapshot.1.png b/MyBankApp/MyBankAppTests/Login/__Snapshots__/LoginViewControllerTests/test_viewDidLoad_shouldMatchSnapshot.1.png new file mode 100644 index 0000000000000000000000000000000000000000..b5ec2527db0db40da18bd2babe2f8251f1682b2d GIT binary patch literal 95141 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 Date: Mon, 3 Jul 2023 02:30:25 -0300 Subject: [PATCH 18/27] =?UTF-8?q?Ajustando=20c=C3=B3digo=20e=20coment?= =?UTF-8?q?=C3=A1rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyBankApp/MyBankApp/Home/Service/HomeService.swift | 2 +- MyBankApp/MyBankApp/Network/NetworkProtocols.swift | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/MyBankApp/MyBankApp/Home/Service/HomeService.swift b/MyBankApp/MyBankApp/Home/Service/HomeService.swift index f3b20d405..9dc60ee80 100644 --- a/MyBankApp/MyBankApp/Home/Service/HomeService.swift +++ b/MyBankApp/MyBankApp/Home/Service/HomeService.swift @@ -15,7 +15,7 @@ final class HomeService: HomeServiceProtocol { for id: String, completion: @escaping (Result) -> Void ) { - //id should be used at the end of the URL, but as the API is not working properly, i will not add the id at the end + /// id should be used at the end of the URL(.../statements/{id}), but as the API working without it, i will not add the id at the end guard let url = URL(string: "https://6092aef785ff5100172136c2.mockapi.io/api/statements") else { completion(.failure(.invalidResponse)) return diff --git a/MyBankApp/MyBankApp/Network/NetworkProtocols.swift b/MyBankApp/MyBankApp/Network/NetworkProtocols.swift index 17d3d29a6..9f02f0202 100644 --- a/MyBankApp/MyBankApp/Network/NetworkProtocols.swift +++ b/MyBankApp/MyBankApp/Network/NetworkProtocols.swift @@ -1,8 +1,12 @@ import Foundation protocol NetworkService { - func request(url: URL, method: HTTPMethod, bodyParameters: [String: Any]?, - completion: @escaping (Result) -> Void) + func request( + url: URL, + method: HTTPMethod, + bodyParameters: [String: Any]?, + completion: @escaping (Result) -> Void + ) } protocol URLSessionProtocol { From 1e9921f1490aceb8f87f1de8c6852ac6f6c058d7 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Mon, 3 Jul 2023 03:29:20 -0300 Subject: [PATCH 19/27] Removendo alguns warnings. Ajustando nomenclatura de alguns testes. Criando testes para NetworkServiceImpl. --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 8 ++ .../MyBankApp/Home/Scene/HomeInteractor.swift | 6 +- .../Login/Models/LoginResponse.swift | 2 +- .../Home/HomeServiceTests.swift | 4 +- .../Login/LoginServiceTests.swift | 4 +- .../Network/NetworkServiceImplTests.swift | 109 ++++++++++++++++++ .../Network/URLSessionProtocolSpy.swift | 34 ++++++ 7 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 MyBankApp/MyBankAppTests/Network/NetworkServiceImplTests.swift create mode 100644 MyBankApp/MyBankAppTests/Network/URLSessionProtocolSpy.swift diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index 4ce7640f8..663b3c41a 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 1999FDEFC6310BFE84329E86 /* Pods_MyBankAppTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C337E965EE80D701CC09ED0D /* Pods_MyBankAppTests.framework */; }; 1D3DCA54D867A351A2E52696 /* Pods_MyBankApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CEB3D9AED855921215D3A98 /* Pods_MyBankApp.framework */; }; 4F5A4BD5DE23F65DE5F0A06E /* Pods_MyBankApp_MyBankAppUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39BB90D19EE7C0FCD034B3EF /* Pods_MyBankApp_MyBankAppUITests.framework */; }; + 5EA5DF402A52959300147F4A /* URLSessionProtocolSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA5DF3F2A52959300147F4A /* URLSessionProtocolSpy.swift */; }; + 5EA5DF422A52965F00147F4A /* NetworkServiceImplTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA5DF412A52965F00147F4A /* NetworkServiceImplTests.swift */; }; 5EEFAB4E2A50982B00A000EE /* NetworkProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB4D2A50982B00A000EE /* NetworkProtocols.swift */; }; 5EEFAB572A50A81900A000EE /* LoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB562A50A81900A000EE /* LoginService.swift */; }; 5EEFAB592A50A84300A000EE /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB582A50A84300A000EE /* NetworkError.swift */; }; @@ -87,6 +89,8 @@ 39BB90D19EE7C0FCD034B3EF /* Pods_MyBankApp_MyBankAppUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MyBankApp_MyBankAppUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3CEB3D9AED855921215D3A98 /* Pods_MyBankApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MyBankApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4FF8791C0A8C3F3ECC7E00B1 /* Pods-MyBankApp-MyBankAppUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyBankApp-MyBankAppUITests.release.xcconfig"; path = "Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests.release.xcconfig"; sourceTree = ""; }; + 5EA5DF3F2A52959300147F4A /* URLSessionProtocolSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionProtocolSpy.swift; sourceTree = ""; }; + 5EA5DF412A52965F00147F4A /* NetworkServiceImplTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkServiceImplTests.swift; sourceTree = ""; }; 5EEFAB4D2A50982B00A000EE /* NetworkProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtocols.swift; sourceTree = ""; }; 5EEFAB562A50A81900A000EE /* LoginService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginService.swift; sourceTree = ""; }; 5EEFAB582A50A84300A000EE /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; @@ -250,6 +254,8 @@ isa = PBXGroup; children = ( 5EEFAB6B2A50B16E00A000EE /* NetworkServiceSpy.swift */, + 5EA5DF3F2A52959300147F4A /* URLSessionProtocolSpy.swift */, + 5EA5DF412A52965F00147F4A /* NetworkServiceImplTests.swift */, ); path = Network; sourceTree = ""; @@ -699,6 +705,8 @@ 5EEFABE32A522C5400A000EE /* LoginBusinessLogicSpy.swift in Sources */, 5EEFABE02A522B5000A000EE /* LoginViewControllerTests.swift in Sources */, 5EEFABFC2A52770E00A000EE /* LoginViewControllerSpy.swift in Sources */, + 5EA5DF402A52959300147F4A /* URLSessionProtocolSpy.swift in Sources */, + 5EA5DF422A52965F00147F4A /* NetworkServiceImplTests.swift in Sources */, 5EEFABF02A52671E00A000EE /* LoginResponse+Fixture.swift in Sources */, 5EEFABEB2A5265C700A000EE /* LoginInteractorTests.swift in Sources */, 5EEFAB6C2A50B16E00A000EE /* NetworkServiceSpy.swift in Sources */, diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift b/MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift index ee02d69a8..2e735f8c8 100644 --- a/MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift +++ b/MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift @@ -13,16 +13,16 @@ final class HomeInteractor: HomeBusinessLogic, HomeDataStore { } func fetchUserStatements() { - homeService.fetchStatements(for: user?.userId ?? "") { [weak self, presenter] result in + homeService.fetchStatements(for: user?.userId ?? "") { [weak self] result in switch result { case .success(let response): guard let self = self else { return } print("Statements:", response.statement) // statements = response.statement - presenter?.presentStatementsSuccess(response.statement) + self.presenter?.presentStatementsSuccess(response.statement) case .failure(let error): print("Statements error:", error.localizedDescription) - presenter?.presentStatementsError(message: "") + self?.presenter?.presentStatementsError(message: "") } } } diff --git a/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift b/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift index a32de144f..9d94a2636 100644 --- a/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift +++ b/MyBankApp/MyBankApp/Login/Models/LoginResponse.swift @@ -1,6 +1,6 @@ import Foundation -struct LoginResponse: Decodable, Equatable { +struct LoginResponse: Codable, Equatable { let userId: String let email: String let cpf: String diff --git a/MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift b/MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift index 208dc57ae..d45420810 100644 --- a/MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift +++ b/MyBankApp/MyBankAppTests/Home/HomeServiceTests.swift @@ -17,7 +17,7 @@ final class HomeServiceTests: XCTestCase { super.tearDown() } - func testFetchStatementsSuccess() { + func test_fetchStatements_givenSuccessResponse_whenValidUrlAndValidMethod_shouldSucceedWithValidStatements() { // Given let expectedStatements = [ Statement(id: 1, type: "Expense", date: "2023-06-30", detail: "Groceries", value: 50.0), @@ -55,7 +55,7 @@ final class HomeServiceTests: XCTestCase { } } - func testFetchStatementsFailure() { + func test_fetchStatements_givenFailureResponse_shouldFailWithExpectedError() { // Given let expectedError = NetworkError.requestFailed networkServiceSpy.mockedResult = .failure(expectedError) diff --git a/MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift b/MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift index d7c02050f..d0af84468 100644 --- a/MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift +++ b/MyBankApp/MyBankAppTests/Login/LoginServiceTests.swift @@ -17,7 +17,7 @@ final class LoginServiceTests: XCTestCase { super.tearDown() } - func testLoginSuccess() { + func test_givenLoginSuccess_shouldReturnValidLoginResponse() { // Given let expectedID = "user123" let expectedEmail = "fulano@teste.com" @@ -65,7 +65,7 @@ final class LoginServiceTests: XCTestCase { } } - func testLoginFailure() { + func test_givenLoginFailure_shouldFailWithExpectedError() { // Given let expectedError = NetworkError.requestFailed networkServiceSpy.mockedResult = .failure(expectedError) diff --git a/MyBankApp/MyBankAppTests/Network/NetworkServiceImplTests.swift b/MyBankApp/MyBankAppTests/Network/NetworkServiceImplTests.swift new file mode 100644 index 000000000..f837c3c79 --- /dev/null +++ b/MyBankApp/MyBankAppTests/Network/NetworkServiceImplTests.swift @@ -0,0 +1,109 @@ +import XCTest +@testable import MyBankApp + +final class NetworkServiceImplTests: XCTestCase { + + var sut: NetworkServiceImpl! + var urlSessionMock: URLSessionSpy! + + override func setUp() { + super.setUp() + urlSessionMock = URLSessionSpy() + sut = NetworkServiceImpl(urlSession: urlSessionMock) + } + + override func tearDown() { + sut = nil + urlSessionMock = nil + super.tearDown() + } + + func test_request_givenSuccessfulResponseWithValidData_shouldReturnSuccess() { + // Given + let expectedData = try? JSONEncoder().encode(LoginResponse.fixture()) + let expectedResponse = HTTPURLResponse(url: URL(string: "https://example.com")!, statusCode: 200, httpVersion: nil, headerFields: nil) + urlSessionMock.mockedData = expectedData + urlSessionMock.mockedResponse = expectedResponse + + // When + sut.request(url: URL(string: "https://example.com")!, method: .get, bodyParameters: nil) { (result: Result) in + // Then + switch result { + case .success(let data): + XCTAssertEqual(data, LoginResponse.fixture()) + case .failure(_): + XCTFail("Expected successful response") + } + } + } + + func test_request_givenNetworkError_shouldReturnFailureWithRequestFailed() { + // Given + let expectedError = NSError(domain: "TestErrorDomain", code: 123, userInfo: nil) + urlSessionMock.mockedError = expectedError + + // When + sut.request(url: URL(string: "https://example.com")!, method: .get, bodyParameters: nil) { (result: Result) in + // Then + switch result { + case .success(_): + XCTFail("Expected network error") + case .failure(let error): + XCTAssertEqual(error, NetworkError.requestFailed) + } + } + } + + func test_request_givenInvalidStatusCode_shouldReturnFailureWithInvalidResponse() { + // Given + let expectedResponse = HTTPURLResponse(url: URL(string: "https://example.com")!, statusCode: 404, httpVersion: nil, headerFields: nil) + urlSessionMock.mockedResponse = expectedResponse + + // When + sut.request(url: URL(string: "https://example.com")!, method: .get, bodyParameters: nil) { (result: Result) in + // Then + switch result { + case .success(_): + XCTFail("Expected invalid response error") + case .failure(let error): + XCTAssertEqual(error, NetworkError.invalidResponse) + } + } + } + + func test_request_givenResponseSuccessButMissingResponseData_shouldReturnFailureWithDecodingFailed() { + // Given + let expectedResponse = HTTPURLResponse(url: URL(string: "https://example.com")!, statusCode: 200, httpVersion: nil, headerFields: nil) + urlSessionMock.mockedResponse = expectedResponse + + // When + sut.request(url: URL(string: "https://example.com")!, method: .get, bodyParameters: nil) { (result: Result) in + // Then + switch result { + case .success(_): + XCTFail("Expected missing response data error") + case .failure(let error): + XCTAssertEqual(error, NetworkError.decodingFailed) + } + } + } + + func test_request_givenSuccessButWithEmptyResponseData_shouldReturnFailureWithDecodingFailed() { + // Given + let expectedResponse = HTTPURLResponse(url: URL(string: "https://example.com")!, statusCode: 200, httpVersion: nil, headerFields: nil) + let emptyData = Data() + urlSessionMock.mockedData = emptyData + urlSessionMock.mockedResponse = expectedResponse + + // When + sut.request(url: URL(string: "https://example.com")!, method: .get, bodyParameters: nil) { (result: Result) in + // Then + switch result { + case .success(_): + XCTFail("Expected empty response data error") + case .failure(let error): + XCTAssertEqual(error, NetworkError.decodingFailed) + } + } + } +} diff --git a/MyBankApp/MyBankAppTests/Network/URLSessionProtocolSpy.swift b/MyBankApp/MyBankAppTests/Network/URLSessionProtocolSpy.swift new file mode 100644 index 000000000..0b80fe673 --- /dev/null +++ b/MyBankApp/MyBankAppTests/Network/URLSessionProtocolSpy.swift @@ -0,0 +1,34 @@ +@testable import MyBankApp +import UIKit +import Foundation + +final class URLSessionDataTaskMock: URLSessionDataTaskProtocol { + private let completionHandler: () -> Void + + init(completionHandler: @escaping () -> Void) { + self.completionHandler = completionHandler + } + + func resume() { + completionHandler() + } +} + +final class URLSessionSpy: URLSessionProtocol { + var mockedData: Data? + var mockedResponse: URLResponse? + var mockedError: Error? + + func dataTask( + with request: URLRequest, + completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void + ) -> URLSessionDataTaskProtocol { + let data = mockedData + let response = mockedResponse + let error = mockedError + + return URLSessionDataTaskMock { [weak self] in + completionHandler(data, response, error) + } + } +} From f1ac8ac970e57e614b0ecf68e05a8c7561376660 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Mon, 3 Jul 2023 03:39:17 -0300 Subject: [PATCH 20/27] Ajustando local do arquivo DispatchQueueProtocol e passando para final class. --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 16 ++++++++++++---- .../DispatchQueueProtocol.swift} | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) rename MyBankApp/MyBankApp/{Extensions/DispatchQueue+Extension.swift => Common/DispatchQueueProtocol.swift} (77%) diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index 663b3c41a..a71a067be 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 4F5A4BD5DE23F65DE5F0A06E /* Pods_MyBankApp_MyBankAppUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39BB90D19EE7C0FCD034B3EF /* Pods_MyBankApp_MyBankAppUITests.framework */; }; 5EA5DF402A52959300147F4A /* URLSessionProtocolSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA5DF3F2A52959300147F4A /* URLSessionProtocolSpy.swift */; }; 5EA5DF422A52965F00147F4A /* NetworkServiceImplTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA5DF412A52965F00147F4A /* NetworkServiceImplTests.swift */; }; + 5EA5DF452A52A37200147F4A /* DispatchQueueProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA5DF442A52A37100147F4A /* DispatchQueueProtocol.swift */; }; 5EEFAB4E2A50982B00A000EE /* NetworkProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB4D2A50982B00A000EE /* NetworkProtocols.swift */; }; 5EEFAB572A50A81900A000EE /* LoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB562A50A81900A000EE /* LoginService.swift */; }; 5EEFAB592A50A84300A000EE /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFAB582A50A84300A000EE /* NetworkError.swift */; }; @@ -40,7 +41,6 @@ 5EEFABD52A52179A00A000EE /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABD42A52179A00A000EE /* Double+Extension.swift */; }; 5EEFABD72A52181C00A000EE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABD62A52181C00A000EE /* String+Extension.swift */; }; 5EEFABD92A52195600A000EE /* UIFont+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */; }; - 5EEFABDB2A5226CC00A000EE /* DispatchQueue+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABDA2A5226CC00A000EE /* DispatchQueue+Extension.swift */; }; 5EEFABDE2A522A2B00A000EE /* DispatchQueueSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABDD2A522A2B00A000EE /* DispatchQueueSpy.swift */; }; 5EEFABE02A522B5000A000EE /* LoginViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABDF2A522B5000A000EE /* LoginViewControllerTests.swift */; }; 5EEFABE32A522C5400A000EE /* LoginBusinessLogicSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEFABE22A522C5400A000EE /* LoginBusinessLogicSpy.swift */; }; @@ -91,6 +91,7 @@ 4FF8791C0A8C3F3ECC7E00B1 /* Pods-MyBankApp-MyBankAppUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyBankApp-MyBankAppUITests.release.xcconfig"; path = "Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests.release.xcconfig"; sourceTree = ""; }; 5EA5DF3F2A52959300147F4A /* URLSessionProtocolSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionProtocolSpy.swift; sourceTree = ""; }; 5EA5DF412A52965F00147F4A /* NetworkServiceImplTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkServiceImplTests.swift; sourceTree = ""; }; + 5EA5DF442A52A37100147F4A /* DispatchQueueProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueueProtocol.swift; sourceTree = ""; }; 5EEFAB4D2A50982B00A000EE /* NetworkProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtocols.swift; sourceTree = ""; }; 5EEFAB562A50A81900A000EE /* LoginService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginService.swift; sourceTree = ""; }; 5EEFAB582A50A84300A000EE /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; @@ -119,7 +120,6 @@ 5EEFABD42A52179A00A000EE /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; 5EEFABD62A52181C00A000EE /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extension.swift"; sourceTree = ""; }; - 5EEFABDA2A5226CC00A000EE /* DispatchQueue+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Extension.swift"; sourceTree = ""; }; 5EEFABDD2A522A2B00A000EE /* DispatchQueueSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueueSpy.swift; sourceTree = ""; }; 5EEFABDF2A522B5000A000EE /* LoginViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewControllerTests.swift; sourceTree = ""; }; 5EEFABE22A522C5400A000EE /* LoginBusinessLogicSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginBusinessLogicSpy.swift; sourceTree = ""; }; @@ -202,6 +202,14 @@ path = Pods; sourceTree = ""; }; + 5EA5DF432A52A35800147F4A /* Common */ = { + isa = PBXGroup; + children = ( + 5EA5DF442A52A37100147F4A /* DispatchQueueProtocol.swift */, + ); + path = Common; + sourceTree = ""; + }; 5EEFAB442A50739500A000EE /* Network */ = { isa = PBXGroup; children = ( @@ -311,7 +319,6 @@ 5EEFABD42A52179A00A000EE /* Double+Extension.swift */, 5EEFABD62A52181C00A000EE /* String+Extension.swift */, 5EEFABD82A52195600A000EE /* UIFont+Extension.swift */, - 5EEFABDA2A5226CC00A000EE /* DispatchQueue+Extension.swift */, 5EEFABFF2A52854E00A000EE /* Mirror+Extension.swift */, ); path = Extensions; @@ -401,6 +408,7 @@ 5EF1ACAC2A4E47C6002EA972 /* MyBankApp */ = { isa = PBXGroup; children = ( + 5EA5DF432A52A35800147F4A /* Common */, 5EEFABB22A51E56300A000EE /* Extensions */, 5EEFAB5E2A50A8BE00A000EE /* Login */, 5EEFAB5F2A50A8CB00A000EE /* Home */, @@ -657,12 +665,12 @@ buildActionMask = 2147483647; files = ( 5EEFABBC2A51E6C800A000EE /* HomePresenter.swift in Sources */, - 5EEFABDB2A5226CC00A000EE /* DispatchQueue+Extension.swift in Sources */, 5EEFABBE2A51E71200A000EE /* HomeInteractor.swift in Sources */, 5EEFAB622A50A8E500A000EE /* HomeResponse.swift in Sources */, 5EEFAB5D2A50A88600A000EE /* NetworkServiceImpl.swift in Sources */, 5EEFABD52A52179A00A000EE /* Double+Extension.swift in Sources */, 5EEFABB82A51E5CF00A000EE /* HomeViewController.swift in Sources */, + 5EA5DF452A52A37200147F4A /* DispatchQueueProtocol.swift in Sources */, 5EEFAB922A50D04B00A000EE /* LoginInteractor.swift in Sources */, 5EEFABB42A51E56D00A000EE /* UIColor+Extension.swift in Sources */, 5EEFABC72A52133B00A000EE /* StatementTableViewCell.swift in Sources */, diff --git a/MyBankApp/MyBankApp/Extensions/DispatchQueue+Extension.swift b/MyBankApp/MyBankApp/Common/DispatchQueueProtocol.swift similarity index 77% rename from MyBankApp/MyBankApp/Extensions/DispatchQueue+Extension.swift rename to MyBankApp/MyBankApp/Common/DispatchQueueProtocol.swift index e97d4d66b..b7ac2f774 100644 --- a/MyBankApp/MyBankApp/Extensions/DispatchQueue+Extension.swift +++ b/MyBankApp/MyBankApp/Common/DispatchQueueProtocol.swift @@ -4,7 +4,7 @@ protocol DispatchQueueProtocol { func async(execute work: @escaping () -> Void) } -class MainDispatchQueueWrapper: DispatchQueueProtocol { +final class MainDispatchQueueWrapper: DispatchQueueProtocol { func async(execute work: @escaping () -> Void) { DispatchQueue.main.async(execute: work) } From 067cfd42bfd60b2c16085e59acc2c72ad5445ade Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Mon, 3 Jul 2023 04:09:52 -0300 Subject: [PATCH 21/27] =?UTF-8?q?Ajustando=20crash=20na=20LoginViewControl?= =?UTF-8?q?ler=20quando=20voltava=20erro=20da=20API.=20Validando=20campos?= =?UTF-8?q?=20de=20texto=20na=20LoginViewController,=20criando=20extension?= =?UTF-8?q?s=20e=20valida=C3=A7=C3=B5es.=20TODO:=20melhorar=20l=C3=B3gica?= =?UTF-8?q?=20para=20exibir=20erro=20espec=C3=ADfico=20no=20alerta.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/String+Extension.swift | 46 ++++++++++++++++++ .../Login/Scene/LoginInteractor.swift | 28 ++++++----- .../Login/Scene/LoginViewController.swift | 47 ++++++++++++++++--- 3 files changed, 102 insertions(+), 19 deletions(-) diff --git a/MyBankApp/MyBankApp/Extensions/String+Extension.swift b/MyBankApp/MyBankApp/Extensions/String+Extension.swift index 8d4a8d537..03f734eaf 100644 --- a/MyBankApp/MyBankApp/Extensions/String+Extension.swift +++ b/MyBankApp/MyBankApp/Extensions/String+Extension.swift @@ -1,6 +1,25 @@ import UIKit extension String { + var containsUppercaseLetter: Bool { + let uppercaseLetterRegex = ".*[A-Z]+.*" + let uppercaseLetterPredicate = NSPredicate(format: "SELF MATCHES %@", uppercaseLetterRegex) + return uppercaseLetterPredicate.evaluate(with: self) + } + + var containsSpecialCharacter: Bool { + let specialCharacterRegex = ".*[^A-Za-z0-9].*" + let specialCharacterPredicate = NSPredicate(format: "SELF MATCHES %@", specialCharacterRegex) + return specialCharacterPredicate.evaluate(with: self) + } + + func isValidEmail() -> Bool { + let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" + + let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx) + return emailPred.evaluate(with: self) + } + func convertToValidDate() -> String? { let inputFormatter = DateFormatter() inputFormatter.dateFormat = "ddMMyyyy" @@ -15,4 +34,31 @@ extension String { let formattedDate = outputFormatter.string(from: date) return formattedDate } + + func isValidCPF() -> Bool { + let numbers = self.compactMap({Int(String($0))}) + guard numbers.count == 11 && Set(numbers).count != 1 else { return false } + let soma1 = 11 - ( numbers[0] * 10 + + numbers[1] * 9 + + numbers[2] * 8 + + numbers[3] * 7 + + numbers[4] * 6 + + numbers[5] * 5 + + numbers[6] * 4 + + numbers[7] * 3 + + numbers[8] * 2 ) % 11 + let dv1 = soma1 > 9 ? 0 : soma1 + let soma2 = 11 - ( numbers[0] * 11 + + numbers[1] * 10 + + numbers[2] * 9 + + numbers[3] * 8 + + numbers[4] * 7 + + numbers[5] * 6 + + numbers[6] * 5 + + numbers[7] * 4 + + numbers[8] * 3 + + numbers[9] * 2 ) % 11 + let dv2 = soma2 > 9 ? 0 : soma2 + return dv1 == numbers[9] && dv2 == numbers[10] + } } diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift index 3cb3ac99b..87c5ba959 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift @@ -15,18 +15,20 @@ final class LoginInteractor: LoginBusinessLogic, LoginDataStore { } func login(username: String, password: String) { - //presenter?.presentLoginSuccess() ///Uncomment this line, and comment the loginService implementation if Login API is not working - loginService.login(user: username, password: password) { [weak self, presenter] result in - switch result { - case .success(let response): - guard let self = self else { return } - print("Logged in with ID:", response.userId) - user = response - presenter?.presentLoginSuccess() - case .failure(let error): - print("Login error:", error.localizedDescription) - presenter?.presentLoginError(message: "Invalid credentials") - } - } + /// Uncomment this line, and comment the loginService implementation above if Login API is not working, so the app on the simulator can work properly + presenter?.presentLoginSuccess() + +// loginService.login(user: username, password: password) { [weak self, presenter] result in +// switch result { +// case .success(let response): +// guard let self = self else { return } +// print("Logged in with ID:", response.userId) +// user = response +// presenter?.presentLoginSuccess() +// case .failure(let error): +// print("Login error:", error.localizedDescription) +// presenter?.presentLoginError(message: "Invalid credentials") +// } +// } } } diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift index f5af5c700..31628b525 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift @@ -3,6 +3,7 @@ import UIKit class LoginViewController: UIViewController { var interactor: LoginBusinessLogic? var router: (NSObjectProtocol & LoginRoutingLogic & LoginDataPassing)? + private var dispatchQueue: DispatchQueueProtocol // MARK: UI Elements private let imageView: UIImageView = { @@ -41,10 +42,12 @@ class LoginViewController: UIViewController { init( interactor: LoginBusinessLogic? = nil, - router: LoginRouter? = nil + router: LoginRouter? = nil, + dispatchQueue: DispatchQueueProtocol = MainDispatchQueueWrapper() ) { self.interactor = interactor self.router = router + self.dispatchQueue = dispatchQueue super.init(nibName: nil, bundle: nil) } @@ -105,13 +108,43 @@ class LoginViewController: UIViewController { // MARK: Actions @objc func loginButtonTapped() { guard let username = usernameTextField.text, - let password = passwordTextField.text, !username.isEmpty, !password.isEmpty else { - displayLoginError(message: "Please fill user and password fields correctly!") - return - } + let password = passwordTextField.text, + !username.isEmpty, + !password.isEmpty, + validateForm() + else { + displayLoginError(message: "Please fill user and password fields correctly!") + return + } interactor?.login(username: username, password: password) } + + private func validateUserTextField() -> Bool { + let emailRegex = "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" + let numberRegex = "^\\d{11}$" + let userText = usernameTextField.text ?? "" + + if userText.isValidEmail() { + return true + } else if userText.isValidCPF() { + return true + } else { + return false + } + } + + private func validatePasswordTextField() -> Bool { + let password = passwordTextField.text ?? "" + return password.count >= 3 && password.containsUppercaseLetter && password.containsSpecialCharacter + } + + private func validateForm() -> Bool { + let isUserValid = validateUserTextField() + let isPasswordValid = validatePasswordTextField() + + return isUserValid && isPasswordValid + } } extension LoginViewController: LoginDisplayLogic { @@ -122,6 +155,8 @@ extension LoginViewController: LoginDisplayLogic { func displayLoginError(message: String) { let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alertController, animated: true, completion: nil) + dispatchQueue.async { [weak self] in + self?.present(alertController, animated: true, completion: nil) + } } } From d11e2b140bb4117393f27aeaddc97ca86ad1db6a Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Mon, 3 Jul 2023 10:02:25 -0300 Subject: [PATCH 22/27] Arquivo ReadMe alterado com detalhes do projeto. --- README.md | 56 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index c059fa6a5..29f2eae9c 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,41 @@ -# Show me the code +# Matheus Fusco - Desafio Banco Safra - Kaspper -Esse repositório contem todo o material necessário para realizar o teste: -- A especificação do layout está na pasta 'bank_app_layout' abrindo o index.html, os icones estão na pasta 'assets' +Repositório com o código referente ao desafio proposto para a vaga de iOS no Banco Safra - Kaspper. -- Os dados da Api estão mockados, os exemplos e a especificação dos serviços (login e statements) se encontram no arquivo BankApp.postman_collection.json ( é necessário instalar o postman e importar a colection https://www.getpostman.com/apps) +# Setup -![Image of Yaktocat](https://github.com/SantanderTecnologia/TesteiOSv2/blob/master/telas.png) +Solução adotada para gerenciador de dependência: `Cocoapods` +- Manual de instalação em [link](https://cocoapods.org). +- Entrar na pasta via terminal .../TesteiOSV2/MyBankApp/ e rodar o comando `pod install` +- Após a instalação, rodar o comando `open MyBankApp.xcworkspace` +- PS: Acabei não utilizando nenhuma dependência (pelo menos por enquanto), deixei o mais nativo possível, incluindo na parte de chamadas -### # DESAFIO: +# Como rodar -Na primeira tela teremos um formulario de login, o campo user deve aceitar email ou cpf, -o campo password deve validar se a senha tem pelo menos uma letra maiuscula, um caracter especial e um caracter alfanumérico. -Apos a validação, realizar o login no endpoint https://6092aef785ff5100172136c2.mockapi.io/api/login e exibir os dados de retorno na próxima tela. -O ultimo usuário logado deve ser salvo de forma segura localmente, e exibido na tela de login se houver algum salvo. +- Após a abertuda o `MyBankApp.xcworkspace`, rodar o app com o comando `Command + R` +- PS: Para que a navegação funcione corretamente, no arquivo `LoginInteractor` é necessário descomentar a linha 19, e comentar as linhas 21 até 32. Motivo: Não consegui fazer a API de Login funcionar, então deixei a linha 19 pra fins de navegação do aplicativo -Na segunda tela será exibido os dados formatados do retorno do login e será necessário fazer um segundo request para obter os lançamentos do usuário, no endpoint https://6092aef785ff5100172136c2.mockapi.io/api/statements/{idUser} que retornará uma lista de lançamentos +# Testes -### # Avaliação +Solução adotada para os testes unitários: `XCTests` +Solução adotada para testes de UI: `SnapshotTesting` +- Após a abertura do projeto, rodar `Command + U` -Você será avaliado pela usabilidade, por respeitar o design e pela arquitetura do app. É esperado que você consiga explicar as decisões que tomou durante o desenvolvimento através de commits. +## Observações -Obrigatórios: +- Testes referentes a `Home` ainda não foram finalizados, constando somente o teste da `HomeService` +- Testes referentes a tela de `Login` foram todos finalizados, incluindo todas camadas da arquitetura +- Testes das camadas de `Network` foram todos finalizados +- Para que os testes funcionem corretamente, no arquivo `LoginInteractor` é necessário comentar a linha 19, e descomentar as linhas 21 até 32. Motivo: Não consegui fazer a API de Login funcionar, então deixei a linha 19 pra fins de navegação do aplicativo -* Swift 3.0 ou superior -* Autolayout -* O app deve funcionar no iOS 11 -* Testes unitários, pode usar a ferramenta que você tem mais experiência, só nos explique o que ele tem de bom. -* Arquitetura a ser utilizada: Swift Clean ([https://clean-swift.com/handbook/](https://clean-swift.com/handbook/) && [https://github.com/Clean-Swift/CleanStore](https://github.com/Clean-Swift/CleanStore) -* Uso do git. +# Observações Gerais -### # Observações gerais +- Nunca havia utilizado a arquitetura CleanSwift, espero que tenha feito um bom código +- A API de Login não estava funcionando, defini um retorno `LoginResponse` e seus respectivos campos e utilizei um mock para apresentar a tela `Home` +- A API de `/statements` não funcionava com o `/{id}` no final, então modifiquei a URL para igual a collection enviada para que funcionasse o retorno (sem o `/{id}` no final) -Adicione um arquivo [README.md](http://README.md) com os procedimentos para executar o projeto. -Pedimos que trabalhe sozinho e não divulgue o resultado na internet. +# Próximos passos -Faça um fork desse desse repositório em seu Github e ao finalizar nos envie um Pull Request com o resultado, por favor informe por qual empresa você esta se candidatando. - -# Importante: não há prazo de entrega, faça com qualidade! - -# BOA SORTE! +- Finalizar os testes referente a `Home`, incluindo testes unitários +- Ajustar validação dos campos de texto e retornos exibidos ao usuário +- Adicionar `Loading` tanto na tela de `Login` quanto na `Home` e tratar possíveis cenários de erro na tela \ No newline at end of file From bd6bb6b3ac913eae001888db2fb3d63bcc2f56d2 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Mon, 3 Jul 2023 10:05:34 -0300 Subject: [PATCH 23/27] =?UTF-8?q?Removendo=20coment=C3=A1rios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyBankApp/MyBankApp/Extensions/String+Extension.swift | 2 +- MyBankApp/MyBankAppUITests/MyBankAppUITests.swift | 7 ------- .../MyBankAppUITests/MyBankAppUITestsLaunchTests.swift | 7 ------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/MyBankApp/MyBankApp/Extensions/String+Extension.swift b/MyBankApp/MyBankApp/Extensions/String+Extension.swift index 03f734eaf..bf07c225b 100644 --- a/MyBankApp/MyBankApp/Extensions/String+Extension.swift +++ b/MyBankApp/MyBankApp/Extensions/String+Extension.swift @@ -25,7 +25,7 @@ extension String { inputFormatter.dateFormat = "ddMMyyyy" guard let date = inputFormatter.date(from: self) else { - return nil // Unable to parse the input date string + return nil } let outputFormatter = DateFormatter() diff --git a/MyBankApp/MyBankAppUITests/MyBankAppUITests.swift b/MyBankApp/MyBankAppUITests/MyBankAppUITests.swift index 63ba75f8a..cf40341fc 100644 --- a/MyBankApp/MyBankAppUITests/MyBankAppUITests.swift +++ b/MyBankApp/MyBankAppUITests/MyBankAppUITests.swift @@ -1,10 +1,3 @@ -// -// MyBankAppUITests.swift -// MyBankAppUITests -// -// Created by Matheus Fusco on 29/06/23. -// - import XCTest final class MyBankAppUITests: XCTestCase { diff --git a/MyBankApp/MyBankAppUITests/MyBankAppUITestsLaunchTests.swift b/MyBankApp/MyBankAppUITests/MyBankAppUITestsLaunchTests.swift index d1edb3dce..90286c88f 100644 --- a/MyBankApp/MyBankAppUITests/MyBankAppUITestsLaunchTests.swift +++ b/MyBankApp/MyBankAppUITests/MyBankAppUITestsLaunchTests.swift @@ -1,10 +1,3 @@ -// -// MyBankAppUITestsLaunchTests.swift -// MyBankAppUITests -// -// Created by Matheus Fusco on 29/06/23. -// - import XCTest final class MyBankAppUITestsLaunchTests: XCTestCase { From 1b69b03a21559b3f04e2c493d92112c7703e3135 Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Mon, 3 Jul 2023 10:49:23 -0300 Subject: [PATCH 24/27] Ajustando Test iOS Deployment Target para igualar ao target principal, para rodar corretamente em outros macs. --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index a71a067be..54d76347b 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -949,7 +949,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = D67XBEEN3L; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = fusco.MyBankAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -969,7 +969,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = D67XBEEN3L; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = fusco.MyBankAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; From 3789556211d2e6da0601a06b238b0e831fcbbf06 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 3 Jul 2023 11:16:40 -0300 Subject: [PATCH 25/27] =?UTF-8?q?adicionando=20mais=20testes=20de=20valica?= =?UTF-8?q?=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 4 +-- .../xcshareddata/xcschemes/MyBankApp.xcscheme | 5 ++-- .../Login/Scene/LoginInteractor.swift | 26 +++++++++---------- .../Login/LoginInteractorTests.swift | 4 +-- .../Login/LoginViewControllerTests.swift | 24 +++++++++++++++++ 5 files changed, 44 insertions(+), 19 deletions(-) diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index a71a067be..54d76347b 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -949,7 +949,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = D67XBEEN3L; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = fusco.MyBankAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -969,7 +969,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = D67XBEEN3L; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = fusco.MyBankAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/MyBankApp/MyBankApp.xcodeproj/xcshareddata/xcschemes/MyBankApp.xcscheme b/MyBankApp/MyBankApp.xcodeproj/xcshareddata/xcschemes/MyBankApp.xcscheme index 5cd5c6b51..e32995892 100644 --- a/MyBankApp/MyBankApp.xcodeproj/xcshareddata/xcschemes/MyBankApp.xcscheme +++ b/MyBankApp/MyBankApp.xcodeproj/xcshareddata/xcschemes/MyBankApp.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> @@ -27,7 +27,8 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" - shouldAutocreateTestPlan = "YES"> + shouldAutocreateTestPlan = "YES" + codeCoverageEnabled = "YES"> Date: Mon, 3 Jul 2023 12:34:22 -0300 Subject: [PATCH 26/27] Adicionando camada de Worker e seus respectivos testes e spy. --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 16 +++++ .../MyBankApp/Login/Models/LoginRequest.swift | 6 ++ .../Login/Scene/LoginInteractor.swift | 20 +++--- .../Login/Scene/LoginProtocols.swift | 2 +- .../Login/Scene/LoginViewController.swift | 4 +- .../MyBankApp/Login/Scene/LoginWorker.swift | 18 ++++++ .../Login/LoginInteractorTests.swift | 18 +++--- .../Login/LoginViewControllerTests.swift | 4 +- .../Login/LoginWorkerTests.swift | 64 +++++++++++++++++++ .../Login/Spies/LoginBusinessLogicSpy.swift | 6 +- .../Login/Spies/LoginWorkerProtocolSpy.swift | 15 +++++ 11 files changed, 144 insertions(+), 29 deletions(-) create mode 100644 MyBankApp/MyBankApp/Login/Models/LoginRequest.swift create mode 100644 MyBankApp/MyBankApp/Login/Scene/LoginWorker.swift create mode 100644 MyBankApp/MyBankAppTests/Login/LoginWorkerTests.swift create mode 100644 MyBankApp/MyBankAppTests/Login/Spies/LoginWorkerProtocolSpy.swift diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index 54d76347b..4cbf3ce1d 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -10,6 +10,10 @@ 1999FDEFC6310BFE84329E86 /* Pods_MyBankAppTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C337E965EE80D701CC09ED0D /* Pods_MyBankAppTests.framework */; }; 1D3DCA54D867A351A2E52696 /* Pods_MyBankApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CEB3D9AED855921215D3A98 /* Pods_MyBankApp.framework */; }; 4F5A4BD5DE23F65DE5F0A06E /* Pods_MyBankApp_MyBankAppUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39BB90D19EE7C0FCD034B3EF /* Pods_MyBankApp_MyBankAppUITests.framework */; }; + 5E8FB8DA2A53185300B5A792 /* LoginRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8FB8D92A53185300B5A792 /* LoginRequest.swift */; }; + 5E8FB8DC2A5318A400B5A792 /* LoginWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8FB8DB2A5318A400B5A792 /* LoginWorker.swift */; }; + 5E8FB8DE2A531A4300B5A792 /* LoginWorkerProtocolSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8FB8DD2A531A4300B5A792 /* LoginWorkerProtocolSpy.swift */; }; + 5E8FB8E02A531CF800B5A792 /* LoginWorkerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8FB8DF2A531CF800B5A792 /* LoginWorkerTests.swift */; }; 5EA5DF402A52959300147F4A /* URLSessionProtocolSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA5DF3F2A52959300147F4A /* URLSessionProtocolSpy.swift */; }; 5EA5DF422A52965F00147F4A /* NetworkServiceImplTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA5DF412A52965F00147F4A /* NetworkServiceImplTests.swift */; }; 5EA5DF452A52A37200147F4A /* DispatchQueueProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA5DF442A52A37100147F4A /* DispatchQueueProtocol.swift */; }; @@ -89,6 +93,10 @@ 39BB90D19EE7C0FCD034B3EF /* Pods_MyBankApp_MyBankAppUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MyBankApp_MyBankAppUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3CEB3D9AED855921215D3A98 /* Pods_MyBankApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MyBankApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4FF8791C0A8C3F3ECC7E00B1 /* Pods-MyBankApp-MyBankAppUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyBankApp-MyBankAppUITests.release.xcconfig"; path = "Target Support Files/Pods-MyBankApp-MyBankAppUITests/Pods-MyBankApp-MyBankAppUITests.release.xcconfig"; sourceTree = ""; }; + 5E8FB8D92A53185300B5A792 /* LoginRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRequest.swift; sourceTree = ""; }; + 5E8FB8DB2A5318A400B5A792 /* LoginWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginWorker.swift; sourceTree = ""; }; + 5E8FB8DD2A531A4300B5A792 /* LoginWorkerProtocolSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginWorkerProtocolSpy.swift; sourceTree = ""; }; + 5E8FB8DF2A531CF800B5A792 /* LoginWorkerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginWorkerTests.swift; sourceTree = ""; }; 5EA5DF3F2A52959300147F4A /* URLSessionProtocolSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionProtocolSpy.swift; sourceTree = ""; }; 5EA5DF412A52965F00147F4A /* NetworkServiceImplTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkServiceImplTests.swift; sourceTree = ""; }; 5EA5DF442A52A37100147F4A /* DispatchQueueProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueueProtocol.swift; sourceTree = ""; }; @@ -254,6 +262,7 @@ isa = PBXGroup; children = ( 5EEFAB662A50A99C00A000EE /* LoginResponse.swift */, + 5E8FB8D92A53185300B5A792 /* LoginRequest.swift */, ); path = Models; sourceTree = ""; @@ -279,6 +288,7 @@ 5EEFABEA2A5265C700A000EE /* LoginInteractorTests.swift */, 5EEFABF72A526D2900A000EE /* LoginRouterTests.swift */, 5EEFABFD2A52836900A000EE /* LoginConfiguratorTests.swift */, + 5E8FB8DF2A531CF800B5A792 /* LoginWorkerTests.swift */, ); path = Login; sourceTree = ""; @@ -300,6 +310,7 @@ 5EEFAB912A50D04B00A000EE /* LoginInteractor.swift */, 5EEFAB952A50D0C500A000EE /* LoginRouter.swift */, 5EEFAB972A50D15200A000EE /* LoginConfigurator.swift */, + 5E8FB8DB2A5318A400B5A792 /* LoginWorker.swift */, ); path = Scene; sourceTree = ""; @@ -371,6 +382,7 @@ 5EEFABEC2A52667500A000EE /* LoginServiceSpy.swift */, 5EEFABF12A5267E500A000EE /* LoginPresentationLogicSpy.swift */, 5EEFABFB2A52770E00A000EE /* LoginViewControllerSpy.swift */, + 5E8FB8DD2A531A4300B5A792 /* LoginWorkerProtocolSpy.swift */, ); path = Spies; sourceTree = ""; @@ -667,6 +679,8 @@ 5EEFABBC2A51E6C800A000EE /* HomePresenter.swift in Sources */, 5EEFABBE2A51E71200A000EE /* HomeInteractor.swift in Sources */, 5EEFAB622A50A8E500A000EE /* HomeResponse.swift in Sources */, + 5E8FB8DC2A5318A400B5A792 /* LoginWorker.swift in Sources */, + 5E8FB8DA2A53185300B5A792 /* LoginRequest.swift in Sources */, 5EEFAB5D2A50A88600A000EE /* NetworkServiceImpl.swift in Sources */, 5EEFABD52A52179A00A000EE /* Double+Extension.swift in Sources */, 5EEFABB82A51E5CF00A000EE /* HomeViewController.swift in Sources */, @@ -712,11 +726,13 @@ 5EEFABE92A52630E00A000EE /* LoginDisplayLogicSpy.swift in Sources */, 5EEFABE32A522C5400A000EE /* LoginBusinessLogicSpy.swift in Sources */, 5EEFABE02A522B5000A000EE /* LoginViewControllerTests.swift in Sources */, + 5E8FB8DE2A531A4300B5A792 /* LoginWorkerProtocolSpy.swift in Sources */, 5EEFABFC2A52770E00A000EE /* LoginViewControllerSpy.swift in Sources */, 5EA5DF402A52959300147F4A /* URLSessionProtocolSpy.swift in Sources */, 5EA5DF422A52965F00147F4A /* NetworkServiceImplTests.swift in Sources */, 5EEFABF02A52671E00A000EE /* LoginResponse+Fixture.swift in Sources */, 5EEFABEB2A5265C700A000EE /* LoginInteractorTests.swift in Sources */, + 5E8FB8E02A531CF800B5A792 /* LoginWorkerTests.swift in Sources */, 5EEFAB6C2A50B16E00A000EE /* NetworkServiceSpy.swift in Sources */, 5EEFABED2A52667500A000EE /* LoginServiceSpy.swift in Sources */, ); diff --git a/MyBankApp/MyBankApp/Login/Models/LoginRequest.swift b/MyBankApp/MyBankApp/Login/Models/LoginRequest.swift new file mode 100644 index 000000000..2e0bc77f1 --- /dev/null +++ b/MyBankApp/MyBankApp/Login/Models/LoginRequest.swift @@ -0,0 +1,6 @@ +import Foundation + +struct LoginRequest: Equatable, Codable { + let username: String + let password: String +} diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift index 9d265db57..d0d0a2f7e 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift @@ -2,32 +2,28 @@ import Foundation final class LoginInteractor: LoginBusinessLogic, LoginDataStore { var presenter: LoginPresentationLogic? - let loginService: LoginServiceProtocol + var worker: LoginWorkerProtocol var user: LoginResponse? init( presenter: LoginPresentationLogic? = nil, - loginService: LoginServiceProtocol = LoginService() + worker: LoginWorkerProtocol = LoginWorker() ) { self.presenter = presenter - self.loginService = loginService + self.worker = worker } - func login(username: String, password: String) { + func login(request: LoginRequest) { /// Uncomment this line, and comment the loginService implementation above if Login API is not working, so the app on the simulator can work properly // presenter?.presentLoginSuccess() - - loginService.login(user: username, password: password) { [weak self, presenter] result in + worker.login(request: request) { [weak self] result in switch result { case .success(let response): - guard let self = self else { return } - print("Logged in with ID:", response.userId) - self.user = response - presenter?.presentLoginSuccess() + self?.user = response + self?.presenter?.presentLoginSuccess() case .failure(let error): - print("Login error:", error.localizedDescription) - presenter?.presentLoginError(message: "Invalid credentials") + self?.presenter?.presentLoginError(message: error.localizedDescription) } } } diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift b/MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift index 873ad7542..32e61cc17 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginProtocols.swift @@ -8,7 +8,7 @@ protocol LoginDisplayLogic: AnyObject { // MARK: - LoginInteractor protocol LoginBusinessLogic { - func login(username: String, password: String) + func login(request: LoginRequest) } // MARK: - LoginPresenter diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift index 31628b525..8e037ba61 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginViewController.swift @@ -116,8 +116,8 @@ class LoginViewController: UIViewController { displayLoginError(message: "Please fill user and password fields correctly!") return } - - interactor?.login(username: username, password: password) + let request = LoginRequest(username: username, password: password) + interactor?.login(request: request) } private func validateUserTextField() -> Bool { diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginWorker.swift b/MyBankApp/MyBankApp/Login/Scene/LoginWorker.swift new file mode 100644 index 000000000..1b8b28a57 --- /dev/null +++ b/MyBankApp/MyBankApp/Login/Scene/LoginWorker.swift @@ -0,0 +1,18 @@ +import Foundation + +protocol LoginWorkerProtocol { + func login(request: LoginRequest, completion: @escaping (Result) -> Void) +} + +final class LoginWorker: LoginWorkerProtocol { + + private var loginService: LoginServiceProtocol + + init(loginService: LoginServiceProtocol = LoginService()) { + self.loginService = loginService + } + + func login(request: LoginRequest, completion: @escaping (Result) -> Void) { + loginService.login(user: request.username, password: request.password, completion: completion) + } +} diff --git a/MyBankApp/MyBankAppTests/Login/LoginInteractorTests.swift b/MyBankApp/MyBankAppTests/Login/LoginInteractorTests.swift index 1740d051f..2bee85793 100644 --- a/MyBankApp/MyBankAppTests/Login/LoginInteractorTests.swift +++ b/MyBankApp/MyBankAppTests/Login/LoginInteractorTests.swift @@ -4,37 +4,37 @@ import XCTest final class LoginInteractorTests: XCTestCase { private var sut: LoginInteractor! private var presenterSpy: LoginPresentationLogicSpy! - private var loginServiceSpy: LoginServiceSpy! + private var workerSpy: LoginWorkerProtocolSpy! override func setUp() { super.setUp() presenterSpy = LoginPresentationLogicSpy() - loginServiceSpy = LoginServiceSpy() - sut = LoginInteractor(presenter: presenterSpy, loginService: loginServiceSpy) + workerSpy = LoginWorkerProtocolSpy() + sut = LoginInteractor(presenter: presenterSpy, worker: workerSpy) } override func tearDown() { presenterSpy = nil - loginServiceSpy = nil + workerSpy = nil sut = nil super.tearDown() } func test_login_givenLoginSuccess_shouldCallPresentLoginSuccess() { - loginServiceSpy.completionToBeReturned = .success(.fixture()) + workerSpy.completionToBeReturned = .success(.fixture()) - sut.login(username: "teste@teste.com", password: "A@3") + sut.login(request: LoginRequest(username: "", password: "")) XCTAssertNotNil(sut.user) XCTAssertEqual(presenterSpy.calledMethods, [.presentLoginSuccess]) } func test_login_givenLoginFailure_shouldCallPresentLoginError() { - loginServiceSpy.completionToBeReturned = .failure(.requestFailed) + workerSpy.completionToBeReturned = .failure(.requestFailed) - sut.login(username: "teste@teste.com", password: "A@3") + sut.login(request: LoginRequest(username: "", password: "")) XCTAssertNil(sut.user) - XCTAssertEqual(presenterSpy.calledMethods, [.presentLoginError(message: "Invalid credentials")]) + XCTAssertEqual(presenterSpy.calledMethods, [.presentLoginError(message: "Request failed")]) } } diff --git a/MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift b/MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift index 2f2aef40a..6bd2b94cb 100644 --- a/MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift +++ b/MyBankApp/MyBankAppTests/Login/LoginViewControllerTests.swift @@ -47,7 +47,7 @@ final class LoginViewControllerTests: XCTestCase { sut.loginButtonTapped() //Then - XCTAssertEqual(interactorSpy.calledMethods, [.login(username: "699.876.200-35", password: "T3st!ng")]) + XCTAssertEqual(interactorSpy.calledMethods, [.login(LoginRequest(username: "699.876.200-35", password: "T3st!ng"))]) } func test_givenUserNameWithInvalidEmailAndPasswordAreFilled_shouldCallInteractorLogin() { @@ -71,7 +71,7 @@ final class LoginViewControllerTests: XCTestCase { sut.loginButtonTapped() //Then - XCTAssertEqual(interactorSpy.calledMethods, [.login(username: "test@test.com", password: "T3st!ng")]) + XCTAssertEqual(interactorSpy.calledMethods, [.login(LoginRequest(username: "699.876.200-35", password: "T3st!ng"))]) } func test_givenUserNameOrPasswordAreNotFilled_shouldNotCallInteractorLogin() { diff --git a/MyBankApp/MyBankAppTests/Login/LoginWorkerTests.swift b/MyBankApp/MyBankAppTests/Login/LoginWorkerTests.swift new file mode 100644 index 000000000..7952e82cf --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/LoginWorkerTests.swift @@ -0,0 +1,64 @@ +@testable import MyBankApp +import XCTest + +final class LoginWorkerTests: XCTestCase { + private var sut: LoginWorker! + private var loginServiceSpy: LoginServiceSpy! + + override func setUp() { + super.setUp() + loginServiceSpy = LoginServiceSpy() + sut = LoginWorker(loginService: loginServiceSpy) + } + + override func tearDown() { + sut = nil + loginServiceSpy = nil + super.tearDown() + } + + func testLoginWithValidCredentials() { + // Given + let request = LoginRequest(username: "", password: "") + let expectedResponse = LoginResponse.fixture() + + loginServiceSpy.completionToBeReturned = .success(expectedResponse) + + // When + let expectation = XCTestExpectation(description: "Login request") + sut.login(request: request) { result in + // Then + switch result { + case .success(let response): + XCTAssertEqual(response, expectedResponse) + case .failure(let error): + XCTFail("Login request failed with error: \(error)") + } + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1.0) + } + + func testLoginWithInvalidCredentials() { + // Given + let request = LoginRequest(username: "", password: "") + + loginServiceSpy.completionToBeReturned = .failure(.invalidResponse) + + // When + let expectation = XCTestExpectation(description: "Login request") + sut.login(request: request) { result in + // Then + switch result { + case .success(_): + XCTFail("Login request should fail with invalid credentials") + case .failure(let error): + XCTAssertEqual(error, .invalidResponse) + } + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1.0) + } +} diff --git a/MyBankApp/MyBankAppTests/Login/Spies/LoginBusinessLogicSpy.swift b/MyBankApp/MyBankAppTests/Login/Spies/LoginBusinessLogicSpy.swift index 5cb1ada74..729bb4480 100644 --- a/MyBankApp/MyBankAppTests/Login/Spies/LoginBusinessLogicSpy.swift +++ b/MyBankApp/MyBankAppTests/Login/Spies/LoginBusinessLogicSpy.swift @@ -3,12 +3,12 @@ import XCTest final class LoginBusinessLogicSpy: LoginBusinessLogic { enum Methods: Equatable { - case login(username: String, password: String) + case login(LoginRequest) } private(set) var calledMethods: [Methods] = [] - func login(username: String, password: String) { - calledMethods.append(.login(username: username, password: password)) + func login(request: LoginRequest) { + calledMethods.append(.login(request)) } } diff --git a/MyBankApp/MyBankAppTests/Login/Spies/LoginWorkerProtocolSpy.swift b/MyBankApp/MyBankAppTests/Login/Spies/LoginWorkerProtocolSpy.swift new file mode 100644 index 000000000..608fa298c --- /dev/null +++ b/MyBankApp/MyBankAppTests/Login/Spies/LoginWorkerProtocolSpy.swift @@ -0,0 +1,15 @@ +@testable import MyBankApp + +final class LoginWorkerProtocolSpy: LoginWorkerProtocol { + enum Method: Equatable { + case login(LoginRequest) + } + + private(set) var calledMethods: [Method] = [] + + var completionToBeReturned: Result = .success(.fixture()) + func login(request: MyBankApp.LoginRequest, completion: @escaping (Result) -> Void) { + calledMethods.append(.login(request)) + completion(completionToBeReturned) + } +} From c4cc5213515540fe2a90d03196b8a76aa86b9dcb Mon Sep 17 00:00:00 2001 From: Matheus Pacheco Fusco Date: Mon, 3 Jul 2023 13:48:49 -0300 Subject: [PATCH 27/27] Adicionando Worker e atualizando classes no fluxo da Home. --- MyBankApp/MyBankApp.xcodeproj/project.pbxproj | 4 ++++ .../Home/Scene/HomeConfigurator.swift | 4 ++++ .../MyBankApp/Home/Scene/HomeInteractor.swift | 13 ++++++------ .../MyBankApp/Home/Scene/HomeWorker.swift | 18 +++++++++++++++++ .../Login/Scene/LoginInteractor.swift | 20 +++++++++---------- 5 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 MyBankApp/MyBankApp/Home/Scene/HomeWorker.swift diff --git a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj index 4cbf3ce1d..ad3dfa1da 100644 --- a/MyBankApp/MyBankApp.xcodeproj/project.pbxproj +++ b/MyBankApp/MyBankApp.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 5E8FB8DC2A5318A400B5A792 /* LoginWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8FB8DB2A5318A400B5A792 /* LoginWorker.swift */; }; 5E8FB8DE2A531A4300B5A792 /* LoginWorkerProtocolSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8FB8DD2A531A4300B5A792 /* LoginWorkerProtocolSpy.swift */; }; 5E8FB8E02A531CF800B5A792 /* LoginWorkerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8FB8DF2A531CF800B5A792 /* LoginWorkerTests.swift */; }; + 5E8FB8E22A53224B00B5A792 /* HomeWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8FB8E12A53224A00B5A792 /* HomeWorker.swift */; }; 5EA5DF402A52959300147F4A /* URLSessionProtocolSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA5DF3F2A52959300147F4A /* URLSessionProtocolSpy.swift */; }; 5EA5DF422A52965F00147F4A /* NetworkServiceImplTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA5DF412A52965F00147F4A /* NetworkServiceImplTests.swift */; }; 5EA5DF452A52A37200147F4A /* DispatchQueueProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA5DF442A52A37100147F4A /* DispatchQueueProtocol.swift */; }; @@ -97,6 +98,7 @@ 5E8FB8DB2A5318A400B5A792 /* LoginWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginWorker.swift; sourceTree = ""; }; 5E8FB8DD2A531A4300B5A792 /* LoginWorkerProtocolSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginWorkerProtocolSpy.swift; sourceTree = ""; }; 5E8FB8DF2A531CF800B5A792 /* LoginWorkerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginWorkerTests.swift; sourceTree = ""; }; + 5E8FB8E12A53224A00B5A792 /* HomeWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeWorker.swift; sourceTree = ""; }; 5EA5DF3F2A52959300147F4A /* URLSessionProtocolSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionProtocolSpy.swift; sourceTree = ""; }; 5EA5DF412A52965F00147F4A /* NetworkServiceImplTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkServiceImplTests.swift; sourceTree = ""; }; 5EA5DF442A52A37100147F4A /* DispatchQueueProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueueProtocol.swift; sourceTree = ""; }; @@ -352,6 +354,7 @@ 5EEFABBD2A51E71200A000EE /* HomeInteractor.swift */, 5EEFABBF2A51EADB00A000EE /* HomeRouter.swift */, 5EEFABC12A51EB1D00A000EE /* HomeConfigurator.swift */, + 5E8FB8E12A53224A00B5A792 /* HomeWorker.swift */, ); path = Scene; sourceTree = ""; @@ -690,6 +693,7 @@ 5EEFABC72A52133B00A000EE /* StatementTableViewCell.swift in Sources */, 5EEFAC002A52854E00A000EE /* Mirror+Extension.swift in Sources */, 5EEFAB4E2A50982B00A000EE /* NetworkProtocols.swift in Sources */, + 5E8FB8E22A53224B00B5A792 /* HomeWorker.swift in Sources */, 5EEFAB802A50CB8500A000EE /* LoginViewController.swift in Sources */, 5EEFABD72A52181C00A000EE /* String+Extension.swift in Sources */, 5EEFAB962A50D0C500A000EE /* LoginRouter.swift in Sources */, diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift b/MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift index c476edfd8..2d595ab2a 100644 --- a/MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift +++ b/MyBankApp/MyBankApp/Home/Scene/HomeConfigurator.swift @@ -6,12 +6,16 @@ final class HomeConfigurator { let interactor = HomeInteractor() let presenter = HomePresenter() let router = HomeRouter() + viewController.interactor = interactor viewController.router = router + interactor.presenter = presenter presenter.viewController = viewController + router.viewController = viewController router.dataStore = interactor + return viewController } } diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift b/MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift index 2e735f8c8..e27bc2847 100644 --- a/MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift +++ b/MyBankApp/MyBankApp/Home/Scene/HomeInteractor.swift @@ -5,24 +5,23 @@ final class HomeInteractor: HomeBusinessLogic, HomeDataStore { var user: LoginResponse? var presenter: HomePresentationLogic? - let homeService: HomeServiceProtocol + let worker: HomeWorkerProtocol - init(presenter: HomePresentationLogic? = nil, homeService: HomeServiceProtocol = HomeService()) { + init(presenter: HomePresentationLogic? = nil, worker: HomeWorkerProtocol = HomeWorker()) { self.presenter = presenter - self.homeService = homeService + self.worker = worker } func fetchUserStatements() { - homeService.fetchStatements(for: user?.userId ?? "") { [weak self] result in + worker.fetchStatements(for: user?.userId ?? "") { [weak self] result in + guard let self = self else { return } switch result { case .success(let response): - guard let self = self else { return } print("Statements:", response.statement) -// statements = response.statement self.presenter?.presentStatementsSuccess(response.statement) case .failure(let error): print("Statements error:", error.localizedDescription) - self?.presenter?.presentStatementsError(message: "") + self.presenter?.presentStatementsError(message: "") } } } diff --git a/MyBankApp/MyBankApp/Home/Scene/HomeWorker.swift b/MyBankApp/MyBankApp/Home/Scene/HomeWorker.swift new file mode 100644 index 000000000..1cdaa4c1a --- /dev/null +++ b/MyBankApp/MyBankApp/Home/Scene/HomeWorker.swift @@ -0,0 +1,18 @@ +import Foundation + +protocol HomeWorkerProtocol { + func fetchStatements(for id: String, completion: @escaping (Result) -> Void) +} + +final class HomeWorker: HomeWorkerProtocol { + + private var homeService: HomeServiceProtocol + + init(homeService: HomeServiceProtocol = HomeService()) { + self.homeService = homeService + } + + func fetchStatements(for id: String, completion: @escaping (Result) -> Void) { + homeService.fetchStatements(for: id, completion: completion) + } +} diff --git a/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift index d0d0a2f7e..b55355fd8 100644 --- a/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift +++ b/MyBankApp/MyBankApp/Login/Scene/LoginInteractor.swift @@ -16,15 +16,15 @@ final class LoginInteractor: LoginBusinessLogic, LoginDataStore { func login(request: LoginRequest) { /// Uncomment this line, and comment the loginService implementation above if Login API is not working, so the app on the simulator can work properly -// presenter?.presentLoginSuccess() - worker.login(request: request) { [weak self] result in - switch result { - case .success(let response): - self?.user = response - self?.presenter?.presentLoginSuccess() - case .failure(let error): - self?.presenter?.presentLoginError(message: error.localizedDescription) - } - } + presenter?.presentLoginSuccess() +// worker.login(request: request) { [weak self] result in +// switch result { +// case .success(let response): +// self?.user = response +// self?.presenter?.presentLoginSuccess() +// case .failure(let error): +// self?.presenter?.presentLoginError(message: error.localizedDescription) +// } +// } } }