diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b57d2b6f..2e0ce1ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,13 +3,17 @@ name: CI Build # Run for any commits to any branch on: [push, pull_request] - env: LANG: en_US.UTF-8 # CodeClimate CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} # Notifications SLACK_WEBHOOK_URL: ${{ secrets.SLACK_URL }} + FOLDER: GoogleFirebase + AWS_REGION: us-east-1 + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + BUILDS_BUCKET: ${{ secrets.AWS_S3_BUILDS_BUCKET }} jobs: @@ -29,11 +33,24 @@ jobs: chmod +x ./cc-test-reporter ./cc-test-reporter before-build - # Executes pod install and runs test against Debug target - - name: Run tests - uses: maierj/fastlane-action@v1.4.0 + # Executes pod install + - name: Installing Dependencies + run: pod install --repo-update + + # Downloads Firebase files + - name: Downloading Google Firebase plist files + run: | + aws s3 cp s3://$BUILDS_BUCKET/$FOLDER/ ios-base/Resources/$FOLDER/ --recursive + + # Runs test on the develop scheme + - name: Running Test Suite + uses: mxcl/xcodebuild@v1 with: - lane: debug_develop + platform: iOS + code-coverage: true + workspace: ios-base.xcworkspace + scheme: ios-base-develop + configuration: Debug # no default, ie. `xcodebuild` decides itself - name: Send test coverage report run: ./cc-test-reporter after-build @@ -44,4 +61,4 @@ jobs: status: ${{ job.status }} text: 'ios-base build status is ${{ job.status }}' fields: repo,message,commit,author,action,eventName,ref,workflow,job,took - if: always() + if: always() diff --git a/6.0.1 b/6.0.1 deleted file mode 100644 index e69de29b..00000000 diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 0007d641..00000000 --- a/Gemfile +++ /dev/null @@ -1,7 +0,0 @@ -source 'https://rubygems.org' - -gem 'slather' -gem "fastlane" -gem "cocoapods" -plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') -eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Podfile b/Podfile index 15d413ca..d6db13bc 100644 --- a/Podfile +++ b/Podfile @@ -2,6 +2,11 @@ platform :ios, '14.0' use_frameworks! inhibit_all_warnings! +def shared_test_dependencies + inherit! :complete + pod 'Swifter', '~> 1.5.0' +end + target 'ios-base' do pod 'RSSwiftNetworking/AlamofireProvider', '~> 1.1.0' pod 'IQKeyboardManagerSwift', '~> 6.1.1' @@ -24,7 +29,10 @@ target 'ios-base' do # ------ target 'ios-baseUITests' do - inherit! :complete - pod 'Swifter', '~> 1.5.0' + shared_test_dependencies + end + + target 'ios-baseUnitTests' do + shared_test_dependencies end end diff --git a/README.md b/README.md index f8f056a3..94d4e395 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://img.shields.io/travis/rootstrap/ios-base/master.svg)](https://travis-ci.org/rootstrap/ios-base) +[![Build Status](https://img.shields.io/github/workflow/status/rootstrap/ios-base/CI%20Build)](https://github.com/rootstrap/ios-base/actions/workflows/ci.yml) [![Maintainability](https://api.codeclimate.com/v1/badges/21b076c80057210cda75/maintainability)](https://codeclimate.com/github/rootstrap/ios-base/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/21b076c80057210cda75/test_coverage)](https://codeclimate.com/github/rootstrap/ios-base/test_coverage) [![License](https://img.shields.io/github/license/rootstrap/ios-base.svg)](https://github.com/rootstrap/ios-base/blob/master/LICENSE.md) @@ -107,29 +107,12 @@ We recommend using [AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/userguid Another alternative for managing sensitive files whithin the repo using Git-Secret can be found in the [**feature/git-secret**](https://github.com/rootstrap/ios-base/tree/feature/jenkins) branch -## CI/CD configuration with Bitrise (updated on Dec 12th 2021) +## CI/CD configuration with Bitrise We are going to start using a tool called Bitrise to configure de CI/CD pipelines for mobiles apps. --> For iOS apps you can find how to do it in this link: https://www.notion.so/rootstrap/iOS-CI-CD-01e00409a0144f5b85212bf889c627dd - -## Automated Build and Deployment using Fastlane (DEPRECATED) - -We use [Fastlane](https://docs.fastlane.tools) to automate code signing, building and release to TestFlight. - -See details in [Fastlane folder](fastlane/README.md). - -## Continuous Integration / Delivery (DEPRECATED) - -We recommend [GitHub Actions](https://docs.github.com/en/actions) for integrating Fastlane into a CI/CD pipeline. You can find two workflows in the GitHub workflows folder: -* [ci.yml](.github/workflows/release.myl) : triggered on any push and PR, runs unit tests, coverage report and static analysis with [CodeClimate](https://github.com/codeclimate/codeclimate) -* [release.yml](.github/workflows/release.yml) : triggered on push to specific branches, builds, signs and submits to TestFlight - -Alternatively you can merge branch [**feature/jenkins**](https://github.com/rootstrap/ios-base/tree/feature/jenkins) for some equivalent CICD boilerplate with **Jenkins**. - -On both alternatives we assume usage of Fastlane match for managing signing Certificates and Profiles, and AWS S3 for storing other files containing third-party keys - ## License iOS-Base is available under the MIT license. See the LICENSE file for more info. diff --git a/fastlane/Appfile b/fastlane/Appfile deleted file mode 100644 index e53ea1ee..00000000 --- a/fastlane/Appfile +++ /dev/null @@ -1,38 +0,0 @@ -# app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app -# apple_id("[[APPLE_ID]]") # Your Apple email address - - -# For more information about the Appfile, see: -# https://docs.fastlane.tools/advanced/#appfile - -for_platform :ios do - - apple_id(ENV['FASTLANE_USER']) - - team_id(ENV['APPLE_TEAM_ID']) - - for_lane :build_develop do - app_identifier('com.rootstrap.ios-base-develop') - end - - for_lane :release_develop do - app_identifier('com.rootstrap.ios-base-develop') - end - - for_lane :build_staging do - app_identifier('com.rootstrap.ios-base-staging') - end - - for_lane :release_staging do - app_identifier('com.rootstrap.ios-base-staging') - end - - for_lane :build_production do - app_identifier('com.rootstrap.ios-base-production') - end - - for_lane :release_production do - app_identifier('com.rootstrap.ios-base-production') - end - -end diff --git a/fastlane/Fastfile b/fastlane/Fastfile deleted file mode 100644 index 39f39020..00000000 --- a/fastlane/Fastfile +++ /dev/null @@ -1,247 +0,0 @@ -# This file contains the fastlane.tools configuration -# You can find the documentation at https://docs.fastlane.tools -# -# For a list of all available actions, check out -# -# https://docs.fastlane.tools/actions -# -# For a list of all available plugins, check out -# -# https://docs.fastlane.tools/plugins/available-plugins -# - -# Uncomment the line if you want fastlane to automatically update itself -# update_fastlane - -skip_docs - -default_platform(:ios) - -# CONFIG VARIABLES -app_name = 'ios-base' -team_id = ENV["APPLE_TEAM_ID"] # The organization's team id in the Apple Developer portal -cert = ENV["APPLE_CERT"] # Local path to distribution certificate file to be used for signing the build -key = ENV["APPLE_KEY"] # Private key (.p12 file) used for encrypting certificate -key_pwd = ENV["APPLE_KEY_PASSWORD"] # Password to private key file -appstore_key_id = ENV["APP_STORE_CONNECT_API_KEY_KEY_ID"] # AppStore Connect API id and issuer -appstore_issuer_id = ENV["APP_STORE_CONNECT_API_KEY_ISSUER_ID"] # -appstore_key_filepath = ENV["APP_STORE_CONNECT_API_KEY_FILE"] # location of .p8 API key file - -# S3 -s3_key = ENV["AWS_ACCESS_KEY_ID"] # credentials for uploading files to S3 -s3_secret_key = ENV["AWS_SECRET_ACCESS_KEY"] # -s3_region = ENV["AWS_REGION"] # -s3_bucket = ENV["BUILDS_BUCKET"] # S3 bucket and parent folder to upload to -folder = ENV["FOLDER"] # - -# Slack -slack_channel = ENV["SLACK_CHANNEL"] # Slack webhook url and channel name for sending notifications upon completion -slack_url = ENV["SLACK_URL"] # - - - -platform :ios do - - lane :set_signing do - # Create keychain - (Travis setup works for GitHhub Actions too) - setup_ci( - force: true, - provider: "travis" - ) - # Unlock keychain and set as default - unlock_keychain( - path: "fastlane_tmp_keychain", - password: "", - set_default: true - ) - # Import .cer and .p12 - this is a workaround for fastlane match when we retrieve certs from a custom location - import_certificate( - certificate_path: key, - certificate_password: key_pwd, - keychain_name: "fastlane_tmp_keychain", - keychain_password: "", - log_output: true - ) - import_certificate( - certificate_path: cert, - keychain_name: "fastlane_tmp_keychain", - keychain_password: "", - log_output: true - ) - end - - lane :build_and_sign do |options| - set_signing - # pod install - cocoapods - gym( - scheme: options[:scheme], - workspace: app_name+'.xcworkspace', - export_method: options[:method], - export_options: {iCloudContainerEnvironment: 'Production'}, - clean: true, - include_bitcode: true, - output_name: options[:scheme]+".ipa" - ) - end - - lane :publish_appstore do |options| - # get timestamp - datetime = sh("date +%Y%m%d%H%M").chomp - # get branch name - branch = git_branch() - # get app version - version = get_version_number(target: options[:scheme]) - changelog_from_git_commits( - pretty: "- (%ae) %s",# Optional, lets you provide a custom format to apply to each commit when generating the changelog text - date_format: "short",# Optional, lets you provide an additional date format to dates within the pretty-formatted string - match_lightweight_tag: false, # Optional, lets you ignore lightweight (non-annotated) tags when searching for the last tag - merge_commit_filtering: "exclude_merges" # Optional, lets you filter out merge commits - ) - # Load an App Store Connect API token - api_key = app_store_connect_api_key( - key_id: appstore_key_id, - issuer_id: appstore_issuer_id, - key_filepath: appstore_key_filepath, - in_house: false # determines this is an AppStore team - ) - # Submit to TestFlight with the previous token - upload_to_testflight( - api_key: api_key, - skip_waiting_for_build_processing: true - ) - # send Slack notification - optional - slack( - message: "Hi! A new iOS "+options[:scheme]+" build has been submitted to TestFlight", - payload: { - "Build Date" => Time.new.to_s, - "Release Version" => version - }, - channel: slack_channel, - slack_url: slack_url, - use_webhook_configured_username_and_icon: true, - fail_on_error: false, - success: true - ) - end - - lane :publish_s3 do |options| - # get timestamp - datetime = sh("date +%Y%m%d%H%M").chomp - # get branch name - branch = git_branch() - # get app version - version = get_version_number(target: options[:scheme]) - # push to S3 - s3_path = folder+"/ios/"+branch+"/"+version+"-"+datetime+"/" - aws_s3( - access_key: s3_key, - secret_access_key: s3_secret_key, - bucket: s3_bucket, - region: s3_region, - ipa: lane_context[SharedValues::IPA_OUTPUT_PATH], - path: s3_path, - acl: "public-read", - upload_metadata: true - ) - # send notification - slack( - message: "Hi! A new iOS build has been uploaded for "+options[:scheme], - payload: { - "Build Date" => Time.new.to_s, - "Release Version" => version, - "Location" => lane_context[SharedValues::S3_FOLDER_OUTPUT_PATH], - "Download link" => "https://"+s3_bucket+".s3.amazonaws.com/"+s3_path+options[:scheme]+".ipa" - }, - channel: slack_channel, - slack_url: slack_url, - use_webhook_configured_username_and_icon: true, - fail_on_error: false, - success: true - ) - end - -#DEVELOP - lane :build_develop do - build_and_sign( - scheme: app_name+'-develop', - method: 'ad-hoc' - ) - end - - lane :share_develop do - build_and_sign( - scheme: app_name+'-develop', - method: 'ad-hoc' - ) - publish_s3( - scheme: app_name+'-develop' - ) - end - - lane :release_develop do - build_and_sign( - scheme: app_name+'-develop', - method: 'app-store' - ) - publish_appstore( - scheme: app_name+'-develop' - ) - end - - #STAGING - lane :build_staging do - build_and_sign( - scheme: app_name+'-staging', - method: 'ad-hoc' - ) - end - - lane :share_staging do - build_and_sign( - scheme: app_name+'-staging', - method: 'ad-hoc' - ) - publish_s3( - scheme: app_name+'-staging' - ) - end - - lane :release_staging do - build_and_sign( - scheme: app_name+'-staging', - method: 'app-store' - ) - publish_appstore( - scheme: app_name+'-staging' - ) - end - - #PRODUCTION - lane :build_production do - build_and_sign( - scheme: app_name+'-production', - method: 'ad-hoc' - ) - end - - lane :share_production do - build_and_sign( - scheme: app_name+'-production', - method: 'ad-hoc' - ) - publish_s3( - scheme: app_name+'-production' - ) - end - - lane :release_production do - build_and_sign( - scheme: app_name+'-production', - method: 'app-store' - ) - publish_appstore( - scheme: app_name+'-production' - ) - end -end diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile deleted file mode 100644 index 79ba9e7d..00000000 --- a/fastlane/Pluginfile +++ /dev/null @@ -1,5 +0,0 @@ -# Autogenerated by fastlane -# -# Ensure this file is checked in to source control! - -gem 'fastlane-plugin-aws_s3' \ No newline at end of file diff --git a/fastlane/README.md b/fastlane/README.md deleted file mode 100644 index 7d2ad0d1..00000000 --- a/fastlane/README.md +++ /dev/null @@ -1,72 +0,0 @@ -Fastlane documentation -================ -We use [Fastlane](https://docs.fastlane.tools/) for automating the iOS application build and submission - -# Installation - -Make sure you have the latest version of the Xcode command line tools installed: - -``` -xcode-select --install -``` - -Install _fastlane_ using -``` -[sudo] gem install fastlane -NV -``` -or alternatively using `brew cask install fastlane` - -# Project setup - -1. Generate certificate and profiles for each target -2. [Disable automatic signing](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html#//apple_ref/doc/uid/TP40005929-CH4-SW7) for each target in XCode and associate to the right provisioning profile -3. For uploading the builds to TestFlight, [AppStore Connect API](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) keys are required -4. For uploading the builds to S3, [AWS keys](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys) with valid permissions are required - -# Required environment variables - -* `FASTLANE_USER` : Your App Store Connect / Apple Developer Portal id used for managing certificates and submitting to the App Store -* `FASTLANE_PASSWORD` : Your App Store Connect / Apple Developer Portal password, usually only needed if you also set the -* `FASTLANE_TEAM_ID` : Developer Portal team id -* `LANG` and `LC_ALL` : These set up the locale your shell and all the commands you execute run at. These need to be set to UTF-8 to work correctly,for example en_US.UTF-8 -* `APPLE_CERT` : Local path to distribution certificate file to be used for signing the build -* `APPLE_KEY` : Private key (.p12 file) used for encrypting certificate -* `APPLE_KEY_PASSWORD` : Password to private key file -* `APP_STORE_CONNECT_API_KEY_KEY_ID` : AppStore Connect API ID -* `APP_STORE_CONNECT_API_KEY_ISSUER_ID` : AppStore Connect issuer ID -* `APP_STORE_CONNECT_API_KEY_FILE` : location of .p8 API key file -* `AWS_ACCESS_KEY_ID` : credentials for uploading files to S3 -* `AWS_SECRET_ACCESS_KEY` -* `AWS_REGION` -* `BUILDS_BUCKET` : S3 bucket to upload the build to -* `SLACK_CHANNEL` : Slack webhook url for sending notifications upon completion -* `SLACK_URL` : Slack channel name - -# Available Actions - -## build_* -* Runs `pod install` -* If needed downloads and installs the corresponding distribution certificate and profile -* Builds and archive corresponding target (.ipa file is kept locally) -``` -fastlane build_develop -``` - -## share_* -* Runs the build steps for the corresponding target -* Gathers build version -* Uploads the resulting .ipa to S3 -* Sends a Slack notification -``` -fastlane share_develop -``` - -## release_* -* Checks for the Git status -* Runs the build steps for the corresponding target -* Generates changelog -* Pushes the resulting .ipa to TestFlight -* Sends a Slack notification -``` -fastlane release_develop -``` diff --git a/ios-base.xcodeproj/project.pbxproj b/ios-base.xcodeproj/project.pbxproj index c7826415..ebd40a44 100644 --- a/ios-base.xcodeproj/project.pbxproj +++ b/ios-base.xcodeproj/project.pbxproj @@ -17,15 +17,29 @@ 0737296024AD389F008C54D9 /* AuthenticationError.json in Resources */ = {isa = PBXBuildFile; fileRef = 0737295B24AD389F008C54D9 /* AuthenticationError.json */; }; 0737296124AD389F008C54D9 /* LogOutSuccessfully.json in Resources */ = {isa = PBXBuildFile; fileRef = 0737295C24AD389F008C54D9 /* LogOutSuccessfully.json */; }; 0737296224AD389F008C54D9 /* SignUpSuccessfully.json in Resources */ = {isa = PBXBuildFile; fileRef = 0737295D24AD389F008C54D9 /* SignUpSuccessfully.json */; }; - 0737296524AD38CE008C54D9 /* NetworkMockerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0737296324AD38CE008C54D9 /* NetworkMockerExtension.swift */; }; + 0737296524AD38CE008C54D9 /* NetworkMocker+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0737296324AD38CE008C54D9 /* NetworkMocker+Stubs.swift */; }; 0737296624AD38CE008C54D9 /* NetworkMocker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0737296424AD38CE008C54D9 /* NetworkMocker.swift */; }; 074889A72477251500A0029E /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074889A62477251500A0029E /* ActivityIndicatorPresenter.swift */; }; - 0748CCCC6E80D2040F1C75D3 /* Pods_ios_base.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A449B00976D690BCC4003AA1 /* Pods_ios_base.framework */; }; 074D20D9248E993B002A39B4 /* AuthenticationServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074D20D8248E993B002A39B4 /* AuthenticationServices.swift */; }; 074D20DB248EA832002A39B4 /* UserServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074D20DA248EA832002A39B4 /* UserServices.swift */; }; 07741D72218CDED600DB3B97 /* FirstViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07741D71218CDED600DB3B97 /* FirstViewModel.swift */; }; + 21EFE678CD3F8778FC4DD4A1 /* Pods_ios_base_ios_baseUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05EB114986F25A947DAF55BD /* Pods_ios_base_ios_baseUITests.framework */; }; 9B0C72711C738D3400BAF3B1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0C72691C738D3400BAF3B1 /* AppDelegate.swift */; }; 9B0C72721C738D3400BAF3B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9B0C726A1C738D3400BAF3B1 /* Assets.xcassets */; }; + 9B0E254728C140E3006FBCD4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9B0E254628C140E3006FBCD4 /* GoogleService-Info.plist */; }; + 9B0E254D28C151D0006FBCD4 /* AuthenticationServicesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E254C28C151D0006FBCD4 /* AuthenticationServicesTests.swift */; }; + 9B0E255028C152A8006FBCD4 /* UserDataManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E254F28C152A8006FBCD4 /* UserDataManagerTests.swift */; }; + 9B0E255228C24B81006FBCD4 /* User+Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E255128C24B81006FBCD4 /* User+Tests.swift */; }; + 9B0E255628C24F96006FBCD4 /* SessionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E255528C24F96006FBCD4 /* SessionManagerTests.swift */; }; + 9B0E255728C2544E006FBCD4 /* NetworkMocker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0737296424AD38CE008C54D9 /* NetworkMocker.swift */; }; + 9B0E255828C25460006FBCD4 /* NetworkMocker+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0737296324AD38CE008C54D9 /* NetworkMocker+Stubs.swift */; }; + 9B0E255A28C254AB006FBCD4 /* LoginSuccessfully.json in Resources */ = {isa = PBXBuildFile; fileRef = 0737295A24AD389F008C54D9 /* LoginSuccessfully.json */; }; + 9B0E255B28C254AB006FBCD4 /* GetProfileSuccessfully.json in Resources */ = {isa = PBXBuildFile; fileRef = 0737295924AD389F008C54D9 /* GetProfileSuccessfully.json */; }; + 9B0E255C28C254AB006FBCD4 /* LogOutSuccessfully.json in Resources */ = {isa = PBXBuildFile; fileRef = 0737295C24AD389F008C54D9 /* LogOutSuccessfully.json */; }; + 9B0E255D28C254AB006FBCD4 /* SignUpSuccessfully.json in Resources */ = {isa = PBXBuildFile; fileRef = 0737295D24AD389F008C54D9 /* SignUpSuccessfully.json */; }; + 9B0E255E28C254AB006FBCD4 /* AuthenticationError.json in Resources */ = {isa = PBXBuildFile; fileRef = 0737295B24AD389F008C54D9 /* AuthenticationError.json */; }; + 9B0E256028C25987006FBCD4 /* GetProfileFailure.json in Resources */ = {isa = PBXBuildFile; fileRef = 9B0E255F28C25987006FBCD4 /* GetProfileFailure.json */; }; + 9B0E256128C26C63006FBCD4 /* GetProfileFailure.json in Resources */ = {isa = PBXBuildFile; fileRef = 9B0E255F28C25987006FBCD4 /* GetProfileFailure.json */; }; 9B2D0100278CB1C2000657BE /* AuthEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2D00FF278CB1C2000657BE /* AuthEndpoint.swift */; }; 9B2D0102278DE207000657BE /* UserEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2D0101278DE207000657BE /* UserEndpoint.swift */; }; 9B2F322C28999F2100D9C710 /* APIClient+Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2F322B28999F2100D9C710 /* APIClient+Application.swift */; }; @@ -36,21 +50,20 @@ 9B5E1438245B2AA00059DF62 /* UserServiceUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5E1437245B2AA00059DF62 /* UserServiceUnitTests.swift */; }; 9B5E1443245B50630059DF62 /* StringExtensionUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5E1442245B50630059DF62 /* StringExtensionUnitTests.swift */; }; 9B5EAB4A232C146D00B9CE3C /* TextFieldExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5EAB49232C146D00B9CE3C /* TextFieldExtension.swift */; }; - 9B715A461E28083600C0C039 /* PlaceholderTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B715A451E28083600C0C039 /* PlaceholderTextView.swift */; }; 9B77E0731E2FB6350020E450 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B77E0721E2FB6350020E450 /* User.swift */; }; 9B77E0751E2FB66F0020E450 /* UserDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B77E0741E2FB66F0020E450 /* UserDataManager.swift */; }; 9B8574D7212C81950063A3E2 /* SignUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8574D6212C81950063A3E2 /* SignUpViewModel.swift */; }; 9B8574D9212C84130063A3E2 /* ImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8574D8212C84130063A3E2 /* ImageExtension.swift */; }; 9B88CC671CAB305900AE60C5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B88CC661CAB305900AE60C5 /* Constants.swift */; }; - 9B8D307020AB47E90050697F /* JSONEncodingExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8D306F20AB47E90050697F /* JSONEncodingExtension.swift */; }; 9B8EB5BD2475890A0082370F /* NetworkState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8EB5BC2475890A0082370F /* NetworkState.swift */; }; 9B8EB5BF247590710082370F /* AuthViewModelStateDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8EB5BE247590710082370F /* AuthViewModelStateDelegate.swift */; }; 9BAFB7BF2114D9910099DC61 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BAFB7BE2114D9910099DC61 /* HomeViewModel.swift */; }; 9BAFB7C32114E3CD0099DC61 /* SignInViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BAFB7C22114E3CD0099DC61 /* SignInViewModel.swift */; }; 9BD01E361F01641E007255E3 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD01E351F01641E007255E3 /* DictionaryExtension.swift */; }; 9BFA84F31C776827009F64E4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BFA84F21C776827009F64E4 /* LaunchScreen.storyboard */; }; + B851CCEB05F5B120379F25DF /* Pods_ios_base.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CA40D9689960D750465B012 /* Pods_ios_base.framework */; }; BB83CFA64940E660BA4A4420 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; - C0EC01A885BD14BD1552DE80 /* Pods_ios_base_ios_baseUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A1760A5E754FEA0591B3C8A /* Pods_ios_base_ios_baseUITests.framework */; }; + BD289BF04FC44C8FB571FA6D /* Pods_ios_base_ios_baseUnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 485F72F34746F5517F7E7B0E /* Pods_ios_base_ios_baseUnitTests.framework */; }; E59EB5E2CE360C64F9DB38FF /* (null) in Frameworks */ = {isa = PBXBuildFile; }; E81171921DE5EFB7003D3DF5 /* ViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81171911DE5EFB7003D3DF5 /* ViewControllerExtension.swift */; }; E8290BFE1D832D9200599960 /* ViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8290BFD1D832D9200599960 /* ViewExtension.swift */; }; @@ -93,6 +106,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 05EB114986F25A947DAF55BD /* Pods_ios_base_ios_baseUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ios_base_ios_baseUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 071CD2612228544700E6D385 /* FontExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontExtension.swift; sourceTree = ""; }; 07276EB023F5CC460089C0AD /* ios-baseUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ios-baseUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 07276EB223F5CC460089C0AD /* ios_baseUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ios_baseUITests.swift; sourceTree = ""; }; @@ -105,17 +119,27 @@ 0737295B24AD389F008C54D9 /* AuthenticationError.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = AuthenticationError.json; sourceTree = ""; }; 0737295C24AD389F008C54D9 /* LogOutSuccessfully.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = LogOutSuccessfully.json; sourceTree = ""; }; 0737295D24AD389F008C54D9 /* SignUpSuccessfully.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = SignUpSuccessfully.json; sourceTree = ""; }; - 0737296324AD38CE008C54D9 /* NetworkMockerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkMockerExtension.swift; sourceTree = ""; }; + 0737296324AD38CE008C54D9 /* NetworkMocker+Stubs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NetworkMocker+Stubs.swift"; sourceTree = ""; }; 0737296424AD38CE008C54D9 /* NetworkMocker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkMocker.swift; sourceTree = ""; }; 074889A62477251500A0029E /* ActivityIndicatorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorPresenter.swift; sourceTree = ""; }; 074D20D8248E993B002A39B4 /* AuthenticationServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServices.swift; sourceTree = ""; }; 074D20DA248EA832002A39B4 /* UserServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserServices.swift; sourceTree = ""; }; 07741D71218CDED600DB3B97 /* FirstViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewModel.swift; sourceTree = ""; }; - 2C8C72195C24B451D2FEA654 /* Pods-ios-base.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base.debug.xcconfig"; path = "Target Support Files/Pods-ios-base/Pods-ios-base.debug.xcconfig"; sourceTree = ""; }; - 6A1760A5E754FEA0591B3C8A /* Pods_ios_base_ios_baseUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ios_base_ios_baseUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 18B6971A49CF5630F7B81E0C /* Pods-ios-base.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base.release.xcconfig"; path = "Target Support Files/Pods-ios-base/Pods-ios-base.release.xcconfig"; sourceTree = ""; }; + 1CA40D9689960D750465B012 /* Pods_ios_base.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ios_base.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 357CCDB2088853F34FB33593 /* Pods-ios-base-ios-baseUITests.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUITests.staging.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests.staging.xcconfig"; sourceTree = ""; }; + 485F72F34746F5517F7E7B0E /* Pods_ios_base_ios_baseUnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ios_base_ios_baseUnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4D27CF6609E56949C1BE4CFE /* Pods-ios-base-ios-baseUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUITests.debug.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests.debug.xcconfig"; sourceTree = ""; }; + 5B0BC77FA89B89E84632FAA3 /* Pods-ios-base-ios-baseUnitTests.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUnitTests.staging.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUnitTests/Pods-ios-base-ios-baseUnitTests.staging.xcconfig"; sourceTree = ""; }; 9B0C72691C738D3400BAF3B1 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; }; 9B0C726A1C738D3400BAF3B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 9B0C726D1C738D3400BAF3B1 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9B0E254628C140E3006FBCD4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 9B0E254C28C151D0006FBCD4 /* AuthenticationServicesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServicesTests.swift; sourceTree = ""; }; + 9B0E254F28C152A8006FBCD4 /* UserDataManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDataManagerTests.swift; sourceTree = ""; }; + 9B0E255128C24B81006FBCD4 /* User+Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "User+Tests.swift"; sourceTree = ""; }; + 9B0E255528C24F96006FBCD4 /* SessionManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManagerTests.swift; sourceTree = ""; }; + 9B0E255F28C25987006FBCD4 /* GetProfileFailure.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = GetProfileFailure.json; sourceTree = ""; }; 9B12AF6B1F269B03005FD465 /* ThirdPartyKeys.example.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = ThirdPartyKeys.example.plist; sourceTree = ""; }; 9B2D00FF278CB1C2000657BE /* AuthEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthEndpoint.swift; sourceTree = ""; }; 9B2D0101278DE207000657BE /* UserEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEndpoint.swift; sourceTree = ""; }; @@ -130,13 +154,11 @@ 9B5E1439245B2AA00059DF62 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9B5E1442245B50630059DF62 /* StringExtensionUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensionUnitTests.swift; sourceTree = ""; }; 9B5EAB49232C146D00B9CE3C /* TextFieldExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldExtension.swift; sourceTree = ""; }; - 9B715A451E28083600C0C039 /* PlaceholderTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = PlaceholderTextView.swift; sourceTree = ""; tabWidth = 2; }; 9B77E0721E2FB6350020E450 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 9B77E0741E2FB66F0020E450 /* UserDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDataManager.swift; sourceTree = ""; }; 9B8574D6212C81950063A3E2 /* SignUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewModel.swift; sourceTree = ""; }; 9B8574D8212C84130063A3E2 /* ImageExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageExtension.swift; sourceTree = ""; }; 9B88CC661CAB305900AE60C5 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; - 9B8D306F20AB47E90050697F /* JSONEncodingExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONEncodingExtension.swift; sourceTree = ""; }; 9B8EB5BC2475890A0082370F /* NetworkState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkState.swift; sourceTree = ""; }; 9B8EB5BE247590710082370F /* AuthViewModelStateDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewModelStateDelegate.swift; sourceTree = ""; }; 9B9C6BFD20C7160700EB0523 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -144,19 +166,19 @@ 9BAFB7C22114E3CD0099DC61 /* SignInViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewModel.swift; sourceTree = ""; }; 9BD01E351F01641E007255E3 /* DictionaryExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DictionaryExtension.swift; sourceTree = ""; }; 9BFA84F21C776827009F64E4 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; - A143F3EC1A6E7D9DE1CA4A68 /* Pods-ios-base.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base.release.xcconfig"; path = "Target Support Files/Pods-ios-base/Pods-ios-base.release.xcconfig"; sourceTree = ""; }; - A449B00976D690BCC4003AA1 /* Pods_ios_base.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ios_base.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - B08689AA3A7C35FB70274F30 /* Pods-ios-base-ios-baseUITests.uitests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUITests.uitests.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests.uitests.xcconfig"; sourceTree = ""; }; - C37D0E0BA2DB34C9FBF07583 /* Pods-ios-base-ios-baseUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUITests.release.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests.release.xcconfig"; sourceTree = ""; }; - C5763904A89F593C577044A0 /* Pods-ios-base-ios-baseUITests.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUITests.staging.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests.staging.xcconfig"; sourceTree = ""; }; - C88232494AFE27E3FBFC69FE /* Pods-ios-base.uitests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base.uitests.xcconfig"; path = "Target Support Files/Pods-ios-base/Pods-ios-base.uitests.xcconfig"; sourceTree = ""; }; - DA3A451D7100561A04D3D350 /* Pods-ios-base-ios-baseUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUITests.debug.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests.debug.xcconfig"; sourceTree = ""; }; + A3634E941E71B5A93BFF9683 /* Pods-ios-base-ios-baseUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUnitTests.release.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUnitTests/Pods-ios-base-ios-baseUnitTests.release.xcconfig"; sourceTree = ""; }; + BF22B306B6EF3149AD601B7D /* Pods-ios-base-ios-baseUITests.uitests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUITests.uitests.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests.uitests.xcconfig"; sourceTree = ""; }; + C21ACBC83B98EC2694374178 /* Pods-ios-base-ios-baseUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUnitTests.debug.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUnitTests/Pods-ios-base-ios-baseUnitTests.debug.xcconfig"; sourceTree = ""; }; + C38496367A3FA02BB7FD7332 /* Pods-ios-base.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base.debug.xcconfig"; path = "Target Support Files/Pods-ios-base/Pods-ios-base.debug.xcconfig"; sourceTree = ""; }; + C76229E5314F62B6F01F4ACC /* Pods-ios-base.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base.staging.xcconfig"; path = "Target Support Files/Pods-ios-base/Pods-ios-base.staging.xcconfig"; sourceTree = ""; }; + D3A940731572555EE856ED09 /* Pods-ios-base.uitests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base.uitests.xcconfig"; path = "Target Support Files/Pods-ios-base/Pods-ios-base.uitests.xcconfig"; sourceTree = ""; }; E81171911DE5EFB7003D3DF5 /* ViewControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ViewControllerExtension.swift; sourceTree = ""; tabWidth = 2; }; E8290BFD1D832D9200599960 /* ViewExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ViewExtension.swift; sourceTree = ""; tabWidth = 2; }; E8290C011D8330A800599960 /* StringExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; tabWidth = 2; }; E8FBB1BE1DD21A32000D6740 /* Session.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; }; E8FBB1C01DD21D18000D6740 /* SessionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = ""; }; - E9C31B61648676473C98614C /* Pods-ios-base.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base.staging.xcconfig"; path = "Target Support Files/Pods-ios-base/Pods-ios-base.staging.xcconfig"; sourceTree = ""; }; + F193585DECDF701D9A03A8E1 /* Pods-ios-base-ios-baseUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUITests.release.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests.release.xcconfig"; sourceTree = ""; }; + F9D7DF4589E7E4A9991E1C55 /* Pods-ios-base-ios-baseUnitTests.uitests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base-ios-baseUnitTests.uitests.xcconfig"; path = "Target Support Files/Pods-ios-base-ios-baseUnitTests/Pods-ios-base-ios-baseUnitTests.uitests.xcconfig"; sourceTree = ""; }; FA54D5871E11C59600F0DBEA /* FirstViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; tabWidth = 2; }; FABDC9211EE1EB2B000DDAC3 /* ConfigurationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationManager.swift; sourceTree = ""; }; FD8C0271269E2F2E003F86CE /* UIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConstants.swift; sourceTree = ""; }; @@ -182,7 +204,7 @@ files = ( BB83CFA64940E660BA4A4420 /* (null) in Frameworks */, E59EB5E2CE360C64F9DB38FF /* (null) in Frameworks */, - C0EC01A885BD14BD1552DE80 /* Pods_ios_base_ios_baseUITests.framework in Frameworks */, + 21EFE678CD3F8778FC4DD4A1 /* Pods_ios_base_ios_baseUITests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -190,7 +212,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 0748CCCC6E80D2040F1C75D3 /* Pods_ios_base.framework in Frameworks */, + B851CCEB05F5B120379F25DF /* Pods_ios_base.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -198,6 +220,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BD289BF04FC44C8FB571FA6D /* Pods_ios_base_ios_baseUnitTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -208,7 +231,7 @@ isa = PBXGroup; children = ( 0737296424AD38CE008C54D9 /* NetworkMocker.swift */, - 0737296324AD38CE008C54D9 /* NetworkMockerExtension.swift */, + 0737296324AD38CE008C54D9 /* NetworkMocker+Stubs.swift */, 0737295824AD389F008C54D9 /* Resources */, 07276EB223F5CC460089C0AD /* ios_baseUITests.swift */, 07276EB423F5CC460089C0AD /* Info.plist */, @@ -222,11 +245,7 @@ 0737295824AD389F008C54D9 /* Resources */ = { isa = PBXGroup; children = ( - 0737295924AD389F008C54D9 /* GetProfileSuccessfully.json */, - 0737295A24AD389F008C54D9 /* LoginSuccessfully.json */, - 0737295B24AD389F008C54D9 /* AuthenticationError.json */, - 0737295C24AD389F008C54D9 /* LogOutSuccessfully.json */, - 0737295D24AD389F008C54D9 /* SignUpSuccessfully.json */, + 9B0E255928C2548F006FBCD4 /* StubResponses */, ); path = Resources; sourceTree = ""; @@ -315,31 +334,36 @@ isa = PBXGroup; children = ( 074889A5247724EF00A0029E /* Protocols */, - 078642F322E669270027BF11 /* Views */, 9BFA84EE1C7767D9009F64E4 /* Models */, ); path = Common; sourceTree = ""; }; - 078642F322E669270027BF11 /* Views */ = { + 12CFFD1CC7E90042DAA6625E /* Frameworks */ = { isa = PBXGroup; children = ( - 9B715A451E28083600C0C039 /* PlaceholderTextView.swift */, + 1CA40D9689960D750465B012 /* Pods_ios_base.framework */, + 05EB114986F25A947DAF55BD /* Pods_ios_base_ios_baseUITests.framework */, + 485F72F34746F5517F7E7B0E /* Pods_ios_base_ios_baseUnitTests.framework */, ); - path = Views; + name = Frameworks; sourceTree = ""; }; 506A29AE0C7D2BF634BD7D7D /* Pods */ = { isa = PBXGroup; children = ( - C37D0E0BA2DB34C9FBF07583 /* Pods-ios-base-ios-baseUITests.release.xcconfig */, - E9C31B61648676473C98614C /* Pods-ios-base.staging.xcconfig */, - A143F3EC1A6E7D9DE1CA4A68 /* Pods-ios-base.release.xcconfig */, - DA3A451D7100561A04D3D350 /* Pods-ios-base-ios-baseUITests.debug.xcconfig */, - 2C8C72195C24B451D2FEA654 /* Pods-ios-base.debug.xcconfig */, - C88232494AFE27E3FBFC69FE /* Pods-ios-base.uitests.xcconfig */, - B08689AA3A7C35FB70274F30 /* Pods-ios-base-ios-baseUITests.uitests.xcconfig */, - C5763904A89F593C577044A0 /* Pods-ios-base-ios-baseUITests.staging.xcconfig */, + C38496367A3FA02BB7FD7332 /* Pods-ios-base.debug.xcconfig */, + D3A940731572555EE856ED09 /* Pods-ios-base.uitests.xcconfig */, + C76229E5314F62B6F01F4ACC /* Pods-ios-base.staging.xcconfig */, + 18B6971A49CF5630F7B81E0C /* Pods-ios-base.release.xcconfig */, + 4D27CF6609E56949C1BE4CFE /* Pods-ios-base-ios-baseUITests.debug.xcconfig */, + BF22B306B6EF3149AD601B7D /* Pods-ios-base-ios-baseUITests.uitests.xcconfig */, + 357CCDB2088853F34FB33593 /* Pods-ios-base-ios-baseUITests.staging.xcconfig */, + F193585DECDF701D9A03A8E1 /* Pods-ios-base-ios-baseUITests.release.xcconfig */, + C21ACBC83B98EC2694374178 /* Pods-ios-base-ios-baseUnitTests.debug.xcconfig */, + F9D7DF4589E7E4A9991E1C55 /* Pods-ios-base-ios-baseUnitTests.uitests.xcconfig */, + 5B0BC77FA89B89E84632FAA3 /* Pods-ios-base-ios-baseUnitTests.staging.xcconfig */, + A3634E941E71B5A93BFF9683 /* Pods-ios-base-ios-baseUnitTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -366,6 +390,54 @@ path = "ios-base"; sourceTree = ""; }; + 9B0E254B28C15003006FBCD4 /* GoogleFirebase */ = { + isa = PBXGroup; + children = ( + 9B0E254628C140E3006FBCD4 /* GoogleService-Info.plist */, + ); + path = GoogleFirebase; + sourceTree = ""; + }; + 9B0E254E28C1529D006FBCD4 /* Cases */ = { + isa = PBXGroup; + children = ( + 9B0E255428C24F87006FBCD4 /* Managers */, + 9B0E255328C24BE3006FBCD4 /* Extensions */, + 9B5E1441245B504C0059DF62 /* Services */, + ); + path = Cases; + sourceTree = ""; + }; + 9B0E255328C24BE3006FBCD4 /* Extensions */ = { + isa = PBXGroup; + children = ( + 9B5E1442245B50630059DF62 /* StringExtensionUnitTests.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 9B0E255428C24F87006FBCD4 /* Managers */ = { + isa = PBXGroup; + children = ( + 9B0E254F28C152A8006FBCD4 /* UserDataManagerTests.swift */, + 9B0E255528C24F96006FBCD4 /* SessionManagerTests.swift */, + ); + path = Managers; + sourceTree = ""; + }; + 9B0E255928C2548F006FBCD4 /* StubResponses */ = { + isa = PBXGroup; + children = ( + 0737295924AD389F008C54D9 /* GetProfileSuccessfully.json */, + 0737295A24AD389F008C54D9 /* LoginSuccessfully.json */, + 0737295B24AD389F008C54D9 /* AuthenticationError.json */, + 0737295C24AD389F008C54D9 /* LogOutSuccessfully.json */, + 0737295D24AD389F008C54D9 /* SignUpSuccessfully.json */, + 9B0E255F28C25987006FBCD4 /* GetProfileFailure.json */, + ); + path = StubResponses; + sourceTree = ""; + }; 9B2D00FC278CAFFC000657BE /* Endpoints */ = { isa = PBXGroup; children = ( @@ -401,7 +473,7 @@ 9B5AFADB1C7205EC002347D6 /* Products */, 9B0C72511C738C3100BAF3B1 /* ios-base */, 506A29AE0C7D2BF634BD7D7D /* Pods */, - A2B617806945C0F51EB3BD85 /* Frameworks */, + 12CFFD1CC7E90042DAA6625E /* Frameworks */, ); indentWidth = 2; sourceTree = ""; @@ -420,7 +492,7 @@ 9B5E1436245B2AA00059DF62 /* ios-baseUnitTests */ = { isa = PBXGroup; children = ( - 9B5E1441245B504C0059DF62 /* Services */, + 9B0E254E28C1529D006FBCD4 /* Cases */, 9B5E1440245B503F0059DF62 /* Extensions */, 9B5E1439245B2AA00059DF62 /* Info.plist */, ); @@ -430,7 +502,7 @@ 9B5E1440245B503F0059DF62 /* Extensions */ = { isa = PBXGroup; children = ( - 9B5E1442245B50630059DF62 /* StringExtensionUnitTests.swift */, + 9B0E255128C24B81006FBCD4 /* User+Tests.swift */, ); path = Extensions; sourceTree = ""; @@ -439,6 +511,7 @@ isa = PBXGroup; children = ( 9B5E1437245B2AA00059DF62 /* UserServiceUnitTests.swift */, + 9B0E254C28C151D0006FBCD4 /* AuthenticationServicesTests.swift */, ); path = Services; sourceTree = ""; @@ -446,6 +519,7 @@ 9B8D306720AB45160050697F /* Resources */ = { isa = PBXGroup; children = ( + 9B0E254B28C15003006FBCD4 /* GoogleFirebase */, 9B8D306820AB451E0050697F /* Localization */, ); path = Resources; @@ -517,15 +591,6 @@ path = Services; sourceTree = ""; }; - A2B617806945C0F51EB3BD85 /* Frameworks */ = { - isa = PBXGroup; - children = ( - A449B00976D690BCC4003AA1 /* Pods_ios_base.framework */, - 6A1760A5E754FEA0591B3C8A /* Pods_ios_base_ios_baseUITests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; E8290BFC1D832D6400599960 /* Extensions */ = { isa = PBXGroup; children = ( @@ -533,7 +598,6 @@ E8290C011D8330A800599960 /* StringExtension.swift */, E81171911DE5EFB7003D3DF5 /* ViewControllerExtension.swift */, E8290BFD1D832D9200599960 /* ViewExtension.swift */, - 9B8D306F20AB47E90050697F /* JSONEncodingExtension.swift */, 9B8574D8212C84130063A3E2 /* ImageExtension.swift */, 071CD2612228544700E6D385 /* FontExtension.swift */, 9B5EAB49232C146D00B9CE3C /* TextFieldExtension.swift */, @@ -571,11 +635,11 @@ isa = PBXNativeTarget; buildConfigurationList = 07276EB723F5CC460089C0AD /* Build configuration list for PBXNativeTarget "ios-baseUITests" */; buildPhases = ( - 971447569E59788E7C493CB8 /* [CP] Check Pods Manifest.lock */, + 73526C47A8E1EFAA1A75DE35 /* [CP] Check Pods Manifest.lock */, 07276EAC23F5CC460089C0AD /* Sources */, 07276EAD23F5CC460089C0AD /* Frameworks */, 07276EAE23F5CC460089C0AD /* Resources */, - 615A91082393B511F0042D1B /* [CP] Embed Pods Frameworks */, + DCED9B4F16B93E74FFC8C620 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -591,14 +655,14 @@ isa = PBXNativeTarget; buildConfigurationList = 9B5AFAEC1C7205EC002347D6 /* Build configuration list for PBXNativeTarget "ios-base" */; buildPhases = ( - 56AD5304A1278E1FC403D34E /* [CP] Check Pods Manifest.lock */, + 136E268935841BFFD4E0F6FE /* [CP] Check Pods Manifest.lock */, 9B22F5581C720E7D003DDCD8 /* ShellScript */, 9B5AFAD71C7205EB002347D6 /* Frameworks */, FE06840022CE7D8300C6294F /* R.swift */, 9B5AFAD61C7205EB002347D6 /* Sources */, 9B5AFAD81C7205EB002347D6 /* Resources */, FE583F0222AEDC6F00EF3CDA /* Crashlytics */, - B28E807AADD115C4CEC66D80 /* [CP] Embed Pods Frameworks */, + CD618B66BE714A27751DC6AF /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -613,9 +677,11 @@ isa = PBXNativeTarget; buildConfigurationList = 9B5E143F245B2AA00059DF62 /* Build configuration list for PBXNativeTarget "ios-baseUnitTests" */; buildPhases = ( + 770AEC8141B6C3D598B87A68 /* [CP] Check Pods Manifest.lock */, 9B5E1431245B2AA00059DF62 /* Sources */, 9B5E1432245B2AA00059DF62 /* Frameworks */, 9B5E1433245B2AA00059DF62 /* Resources */, + A114EE88D4476F5AB93EE449 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -684,6 +750,7 @@ buildActionMask = 2147483647; files = ( 0737295F24AD389F008C54D9 /* LoginSuccessfully.json in Resources */, + 9B0E256028C25987006FBCD4 /* GetProfileFailure.json in Resources */, 0737296224AD389F008C54D9 /* SignUpSuccessfully.json in Resources */, 0737296024AD389F008C54D9 /* AuthenticationError.json in Resources */, 0737296124AD389F008C54D9 /* LogOutSuccessfully.json in Resources */, @@ -696,6 +763,7 @@ buildActionMask = 2147483647; files = ( FDFAAB0B269CD56D0007DB8B /* Localizable.strings in Resources */, + 9B0E254728C140E3006FBCD4 /* GoogleService-Info.plist in Resources */, 9BFA84F31C776827009F64E4 /* LaunchScreen.storyboard in Resources */, 9B0C72721C738D3400BAF3B1 /* Assets.xcassets in Resources */, ); @@ -705,13 +773,19 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9B0E255C28C254AB006FBCD4 /* LogOutSuccessfully.json in Resources */, + 9B0E256128C26C63006FBCD4 /* GetProfileFailure.json in Resources */, + 9B0E255E28C254AB006FBCD4 /* AuthenticationError.json in Resources */, + 9B0E255A28C254AB006FBCD4 /* LoginSuccessfully.json in Resources */, + 9B0E255D28C254AB006FBCD4 /* SignUpSuccessfully.json in Resources */, + 9B0E255B28C254AB006FBCD4 /* GetProfileSuccessfully.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 56AD5304A1278E1FC403D34E /* [CP] Check Pods Manifest.lock */ = { + 136E268935841BFFD4E0F6FE /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -733,13 +807,70 @@ 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; }; - 615A91082393B511F0042D1B /* [CP] Embed Pods Frameworks */ = { + 73526C47A8E1EFAA1A75DE35 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests-frameworks.sh", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ios-base-ios-baseUITests-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; + }; + 770AEC8141B6C3D598B87A68 /* [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-ios-base-ios-baseUnitTests-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; + }; + 9B22F5581C720E7D003DDCD8 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; + }; + A114EE88D4476F5AB93EE449 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ios-base-ios-baseUnitTests/Pods-ios-base-ios-baseUnitTests-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", "${BUILT_PRODUCTS_DIR}/Device/Device.framework", "${BUILT_PRODUCTS_DIR}/FBSDKCoreKit/FBSDKCoreKit.framework", @@ -780,51 +911,64 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ios-base-ios-baseUnitTests/Pods-ios-base-ios-baseUnitTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 971447569E59788E7C493CB8 /* [CP] Check Pods Manifest.lock */ = { + CD618B66BE714A27751DC6AF /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ios-base/Pods-ios-base-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", + "${BUILT_PRODUCTS_DIR}/Device/Device.framework", + "${BUILT_PRODUCTS_DIR}/FBSDKCoreKit/FBSDKCoreKit.framework", + "${BUILT_PRODUCTS_DIR}/FBSDKLoginKit/FBSDKLoginKit.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCrashlytics/FirebaseCrashlytics.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", + "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", + "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework", + "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", + "${BUILT_PRODUCTS_DIR}/R.swift.Library/Rswift.framework", + "${BUILT_PRODUCTS_DIR}/RSFontSizes/RSFontSizes.framework", + "${BUILT_PRODUCTS_DIR}/RSSwiftNetworking/RSSwiftNetworking.framework", + "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ios-base-ios-baseUITests-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Device.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSDKCoreKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSDKLoginKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreDiagnostics.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCrashlytics.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Rswift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RSFontSizes.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RSSwiftNetworking.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", ); 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"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ios-base/Pods-ios-base-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 9B22F5581C720E7D003DDCD8 /* ShellScript */ = { + DCED9B4F16B93E74FFC8C620 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; - }; - B28E807AADD115C4CEC66D80 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ios-base/Pods-ios-base-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", "${BUILT_PRODUCTS_DIR}/Device/Device.framework", "${BUILT_PRODUCTS_DIR}/FBSDKCoreKit/FBSDKCoreKit.framework", @@ -841,6 +985,7 @@ "${BUILT_PRODUCTS_DIR}/RSFontSizes/RSFontSizes.framework", "${BUILT_PRODUCTS_DIR}/RSSwiftNetworking/RSSwiftNetworking.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", + "${BUILT_PRODUCTS_DIR}/Swifter/Swifter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -860,10 +1005,11 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RSFontSizes.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RSSwiftNetworking.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Swifter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ios-base/Pods-ios-base-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ios-base-ios-baseUITests/Pods-ios-base-ios-baseUITests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; FE06840022CE7D8300C6294F /* R.swift */ = { @@ -919,7 +1065,7 @@ 07276EBC23F5CCA70089C0AD /* XCUIApplicationExtension.swift in Sources */, 0737296624AD38CE008C54D9 /* NetworkMocker.swift in Sources */, 07276EBE23F5CCCA0089C0AD /* XCUIElementExtension.swift in Sources */, - 0737296524AD38CE008C54D9 /* NetworkMockerExtension.swift in Sources */, + 0737296524AD38CE008C54D9 /* NetworkMocker+Stubs.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -939,7 +1085,6 @@ E8290C021D8330A800599960 /* StringExtension.swift in Sources */, FE4D636222B2CBB000685161 /* OnboardingRoutes.swift in Sources */, 071CD2622228544700E6D385 /* FontExtension.swift in Sources */, - 9B8D307020AB47E90050697F /* JSONEncodingExtension.swift in Sources */, 074D20D9248E993B002A39B4 /* AuthenticationServices.swift in Sources */, E81171921DE5EFB7003D3DF5 /* ViewControllerExtension.swift in Sources */, 9B3AA3991ED35007005A4D26 /* SignInViewController.swift in Sources */, @@ -971,7 +1116,6 @@ 9B88CC671CAB305900AE60C5 /* Constants.swift in Sources */, FDFAAB07269CCCD30007DB8B /* LabelExtension.swift in Sources */, 9BAFB7BF2114D9910099DC61 /* HomeViewModel.swift in Sources */, - 9B715A461E28083600C0C039 /* PlaceholderTextView.swift in Sources */, FD8C0272269E2F2E003F86CE /* UIConstants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -980,8 +1124,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9B0E255228C24B81006FBCD4 /* User+Tests.swift in Sources */, + 9B0E255028C152A8006FBCD4 /* UserDataManagerTests.swift in Sources */, + 9B0E254D28C151D0006FBCD4 /* AuthenticationServicesTests.swift in Sources */, + 9B0E255728C2544E006FBCD4 /* NetworkMocker.swift in Sources */, 9B5E1443245B50630059DF62 /* StringExtensionUnitTests.swift in Sources */, + 9B0E255628C24F96006FBCD4 /* SessionManagerTests.swift in Sources */, 9B5E1438245B2AA00059DF62 /* UserServiceUnitTests.swift in Sources */, + 9B0E255828C25460006FBCD4 /* NetworkMocker+Stubs.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1003,7 +1153,7 @@ /* Begin XCBuildConfiguration section */ 07276EB823F5CC460089C0AD /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DA3A451D7100561A04D3D350 /* Pods-ios-base-ios-baseUITests.debug.xcconfig */; + baseConfigurationReference = 4D27CF6609E56949C1BE4CFE /* Pods-ios-base-ios-baseUITests.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1032,7 +1182,7 @@ }; 07276EB923F5CC460089C0AD /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C5763904A89F593C577044A0 /* Pods-ios-base-ios-baseUITests.staging.xcconfig */; + baseConfigurationReference = 357CCDB2088853F34FB33593 /* Pods-ios-base-ios-baseUITests.staging.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1059,7 +1209,7 @@ }; 07276EBA23F5CC460089C0AD /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C37D0E0BA2DB34C9FBF07583 /* Pods-ios-base-ios-baseUITests.release.xcconfig */; + baseConfigurationReference = F193585DECDF701D9A03A8E1 /* Pods-ios-base-ios-baseUITests.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1144,7 +1294,7 @@ }; 0737295524AD37BE008C54D9 /* UITests */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C88232494AFE27E3FBFC69FE /* Pods-ios-base.uitests.xcconfig */; + baseConfigurationReference = D3A940731572555EE856ED09 /* Pods-ios-base.uitests.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1176,7 +1326,7 @@ }; 0737295624AD37BE008C54D9 /* UITests */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B08689AA3A7C35FB70274F30 /* Pods-ios-base-ios-baseUITests.uitests.xcconfig */; + baseConfigurationReference = BF22B306B6EF3149AD601B7D /* Pods-ios-base-ios-baseUITests.uitests.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1205,6 +1355,7 @@ }; 0737295724AD37BE008C54D9 /* UITests */ = { isa = XCBuildConfiguration; + baseConfigurationReference = F9D7DF4589E7E4A9991E1C55 /* Pods-ios-base-ios-baseUnitTests.uitests.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -1346,7 +1497,7 @@ }; 9B5AFAED1C7205EC002347D6 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2C8C72195C24B451D2FEA654 /* Pods-ios-base.debug.xcconfig */; + baseConfigurationReference = C38496367A3FA02BB7FD7332 /* Pods-ios-base.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1378,7 +1529,7 @@ }; 9B5AFAEE1C7205EC002347D6 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A143F3EC1A6E7D9DE1CA4A68 /* Pods-ios-base.release.xcconfig */; + baseConfigurationReference = 18B6971A49CF5630F7B81E0C /* Pods-ios-base.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1408,6 +1559,7 @@ }; 9B5E143C245B2AA00059DF62 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = C21ACBC83B98EC2694374178 /* Pods-ios-base-ios-baseUnitTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -1439,6 +1591,7 @@ }; 9B5E143D245B2AA00059DF62 /* Staging */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 5B0BC77FA89B89E84632FAA3 /* Pods-ios-base-ios-baseUnitTests.staging.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -1468,6 +1621,7 @@ }; 9B5E143E245B2AA00059DF62 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A3634E941E71B5A93BFF9683 /* Pods-ios-base-ios-baseUnitTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -1555,7 +1709,7 @@ }; 9BEE614C1C736054002E43B2 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E9C31B61648676473C98614C /* Pods-ios-base.staging.xcconfig */; + baseConfigurationReference = C76229E5314F62B6F01F4ACC /* Pods-ios-base.staging.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; diff --git a/ios-base/Common/Views/PlaceholderTextView.swift b/ios-base/Common/Views/PlaceholderTextView.swift deleted file mode 100644 index da69d7ec..00000000 --- a/ios-base/Common/Views/PlaceholderTextView.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// PlaceholderTextView.swift -// talkative-iOS -// -// Created by German Lopez on 6/6/16. -// Copyright © 2016 Rootstrap Inc. All rights reserved. -// - -import UIKit - -class PlaceholderTextView: UITextView { - - override var text: String! { - didSet { - // First text set, when placeholder is empty - if text.isEmpty && text != placeholder && !self.isFirstResponder { - text = placeholder - return - } - textColor = text == placeholder ? placeholderColor : fontColor - } - } - @IBInspectable var placeholder: String = "" { - didSet { - if text.isEmpty { - text = placeholder - textColor = placeholderColor - } - } - } - @IBInspectable var placeholderColor: UIColor? = .lightGray - var fontColor: UIColor = .black - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - if let txtC = textColor { - self.fontColor = txtC - } - } - - override func awakeFromNib() { - super.awakeFromNib() - - textColor = text == placeholder ? placeholderColor : fontColor - } - - init( - frame: CGRect, - placeholder: String, - placeholderColor: UIColor = .lightGray - ) { - super.init(frame: frame, textContainer: nil) - self.placeholderColor = placeholderColor - self.placeholder = placeholder - if let txtC = textColor { - self.fontColor = txtC - } - } - - override func becomeFirstResponder() -> Bool { - let isFirstResponder = super.becomeFirstResponder() - if text == placeholder && textColor == placeholderColor { - text = "" - } - textColor = fontColor - return isFirstResponder - } - - override func resignFirstResponder() -> Bool { - if text.isEmpty { - text = placeholder - textColor = placeholderColor - } - return super.resignFirstResponder() - } -} diff --git a/ios-base/Extensions/JSONEncodingExtension.swift b/ios-base/Extensions/JSONEncodingExtension.swift deleted file mode 100644 index d322fe4b..00000000 --- a/ios-base/Extensions/JSONEncodingExtension.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// JSONEncodingExtension.swift -// ios-base -// -// Created by German on 5/15/18. -// Copyright © 2018 Rootstrap Inc. All rights reserved. -// - -import Foundation - -extension JSONDecoder { - - func decode( - _ type: T.Type, from dictionary: [String: Any] - ) throws -> T where T: Decodable { - let data = try? JSONSerialization.data(withJSONObject: dictionary, options: []) - return try decode(T.self, from: data ?? Data()) - } -} diff --git a/ios-base/Extensions/ViewControllerExtension.swift b/ios-base/Extensions/ViewControllerExtension.swift index 7b89edb1..40b7b887 100644 --- a/ios-base/Extensions/ViewControllerExtension.swift +++ b/ios-base/Extensions/ViewControllerExtension.swift @@ -24,32 +24,6 @@ extension UIViewController { present(alert, animated: true, completion: nil) } - func goToScreen(withIdentifier identifier: String, - storyboardId: String? = nil, - modally: Bool = false, - viewControllerConfigurationBlock: ((UIViewController) -> Void)? = nil) { - var storyboard = self.storyboard - - if let storyboardId = storyboardId { - storyboard = UIStoryboard(name: storyboardId, bundle: nil) - } - - guard let viewController = - storyboard?.instantiateViewController(withIdentifier: identifier) else { - assert(false, "No view controller found with that identifier") - return - } - - viewControllerConfigurationBlock?(viewController) - - if modally { - present(viewController, animated: true) - } else { - assert(navigationController != nil, "navigation controller is nil") - navigationController?.pushViewController(viewController, animated: true) - } - } - func applyDefaultUIConfigs() { view.backgroundColor = .screenBackground } diff --git a/ios-base/Home/Views/HomeViewController.swift b/ios-base/Home/Views/HomeViewController.swift index 40202347..c97e4022 100644 --- a/ios-base/Home/Views/HomeViewController.swift +++ b/ios-base/Home/Views/HomeViewController.swift @@ -92,6 +92,12 @@ private extension HomeViewController { subviews: [welcomeLabel, logOutButton, deleteAccountButton, getProfileButton] ) activateConstraints() + setupAccessibility() + } + + func setupAccessibility() { + getProfileButton.accessibilityIdentifier = "GetMyProfileButton" + logOutButton.accessibilityIdentifier = "LogoutButton" } private func activateConstraints() { diff --git a/ios-base/Onboarding/Views/SignInViewController.swift b/ios-base/Onboarding/Views/SignInViewController.swift index f4dad3bd..3c5de5d8 100644 --- a/ios-base/Onboarding/Views/SignInViewController.swift +++ b/ios-base/Onboarding/Views/SignInViewController.swift @@ -96,6 +96,13 @@ private extension SignInViewController { ]) activateConstrains() + setupAccessibility() + } + + func setupAccessibility() { + logInButton.accessibilityIdentifier = "SignInButton" + emailField.accessibilityIdentifier = "EmailTextField" + passwordField.accessibilityIdentifier = "PasswordTextField" } func activateConstrains() { diff --git a/ios-base/Onboarding/Views/SignUpViewController.swift b/ios-base/Onboarding/Views/SignUpViewController.swift index d2e6b7c1..aaf013e6 100644 --- a/ios-base/Onboarding/Views/SignUpViewController.swift +++ b/ios-base/Onboarding/Views/SignUpViewController.swift @@ -108,6 +108,14 @@ private extension SignUpViewController { ]) activateConstrains() + setupAccessibility() + } + + func setupAccessibility() { + signUpButton.accessibilityIdentifier = "SignUpButton" + emailField.accessibilityIdentifier = "EmailTextField" + passwordField.accessibilityIdentifier = "PasswordTextField" + passwordConfirmationField.accessibilityIdentifier = "ConfirmPasswordTextField" } func activateConstrains() { diff --git a/ios-baseUITests/NetworkMocker+Stubs.swift b/ios-baseUITests/NetworkMocker+Stubs.swift new file mode 100644 index 00000000..2e3cbc50 --- /dev/null +++ b/ios-baseUITests/NetworkMocker+Stubs.swift @@ -0,0 +1,80 @@ +// +// NetworkMockerExtension.swift +// ios-baseUITests +// +// Created by Germán Stábile on 6/30/20. +// Copyright © 2020 Rootstrap. All rights reserved. +// + +import Foundation +import RSSwiftNetworking + +internal enum NetworkStub { + case signUp(success: Bool) + case signIn(success: Bool) + case logOut + case profile(success: Bool) + case deleteAccount + + var urlString: String { + switch self { + case .signUp: + return "/users/" + case .signIn: + return "/users/sign_in" + case .logOut: + return "/users/sign_out" + case .profile: + return "/user/profile" + case .deleteAccount: + return "/user/delete_account" + } + } + + var responseFileName: String { + switch self { + case .signUp(let success): + return success ? "SignUpSuccessfully" : "AuthenticationError" + case .signIn(let success): + return success ? "LoginSuccessfully" : "AuthenticationError" + case .logOut, .deleteAccount: + return "LogOutSuccessfully" + case .profile(let success): + return success ? "GetProfileSuccessfully" : "GetProfileFailure" + } + } + + var headers: [String: String]? { + switch self { + case .signUp(let success), .signIn(let success): + if success { + return [ + HTTPHeader.uid.rawValue: "uid", + HTTPHeader.client.rawValue: "client", + HTTPHeader.token.rawValue: "accessToken", + HTTPHeader.expiry.rawValue: "\(Date.distantFuture.timeIntervalSinceNow)", + HTTPHeader.contentType.rawValue: "application/json" + ] + } + return nil + default: + return nil + } + } +} + +extension NetworkMocker { + + func stub( + with networkStub: NetworkStub, + method: HTTPMethod, + customHeaders: [String: String]? = nil + ) { + stub( + url: networkStub.urlString, + responseFilename: networkStub.responseFileName, + method: method, + customHeaders: customHeaders ?? networkStub.headers + ) + } +} diff --git a/ios-baseUITests/NetworkMocker.swift b/ios-baseUITests/NetworkMocker.swift index 2e04a900..9d38cea4 100644 --- a/ios-baseUITests/NetworkMocker.swift +++ b/ios-baseUITests/NetworkMocker.swift @@ -8,6 +8,7 @@ import Foundation import Swifter +import XCTest enum HTTPMethod { case POST @@ -16,9 +17,9 @@ enum HTTPMethod { case DELETE } -class NetworkMocker { +internal class NetworkMocker { - var server = HttpServer() + private(set) lazy var server = HttpServer() func setUp() throws { try server.start() @@ -28,37 +29,49 @@ class NetworkMocker { server.stop() } - public func setupStub( + public func stub( url: String, responseFilename: String, - method: HTTPMethod = .GET + method: HTTPMethod = .GET, + customHeaders: [String: String]? = nil ) { let testBundle = Bundle(for: type(of: self)) let filePath = testBundle.path(forResource: responseFilename, ofType: "json") ?? "" - let fileUrl = URL(fileURLWithPath: filePath) - guard let data = try? Data(contentsOf: fileUrl, options: .uncached) else { - fatalError("Could not parse mocked data") - } - let json = dataToJSON(data: data) - - let response: ((HttpRequest) -> HttpResponse) = { _ in - HttpResponse.ok(.json(json as AnyObject)) - } - - switch method { - case .GET: server.GET[url] = response - case .POST: server.POST[url] = response - case .DELETE: server.DELETE[url] = response - case .PUT: server.PUT[url] = response - } - } - - func dataToJSON(data: Data) -> Any? { + let fileURL = URL(fileURLWithPath: filePath) + do { - return try JSONSerialization.jsonObject(with: data, options: .mutableContainers) + let data = try Data(contentsOf: fileURL, options: .uncached) + let jsonObject = try data.jsonObject() + + let response: ((HttpRequest) -> HttpResponse) = { _ in + if let customHeaders = customHeaders { + return HttpResponse.raw(200, "OK", customHeaders) { bodyWriter in + try bodyWriter.write(data) + } + } + guard let jsonObject = jsonObject else { + XCTFail("A valid JSON couldn't be parsed") + return .badRequest(.none) + } + return HttpResponse.ok(.json(jsonObject)) + } + + switch method { + case .GET: server.GET[url] = response + case .POST: server.POST[url] = response + case .DELETE: server.DELETE[url] = response + case .PUT: server.PUT[url] = response + } } catch let error { - print(error) + XCTFail("Failed to serialize JSON. Error \(error)") } - return nil + } +} + +internal extension Data { + func jsonObject( + options: JSONSerialization.ReadingOptions = .mutableContainers + ) throws -> Any? { + try JSONSerialization.jsonObject(with: self, options: options) } } diff --git a/ios-baseUITests/NetworkMockerExtension.swift b/ios-baseUITests/NetworkMockerExtension.swift deleted file mode 100644 index 47be6b54..00000000 --- a/ios-baseUITests/NetworkMockerExtension.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// NetworkMockerExtension.swift -// ios-baseUITests -// -// Created by Germán Stábile on 6/30/20. -// Copyright © 2020 Rootstrap. All rights reserved. -// - -import Foundation - -extension NetworkMocker { - - func stubSignUp(shouldSucceed: Bool = true) { - let responseFileName = shouldSucceed ? "SignUpSuccessfully" : "AuthenticationError" - - setupStub( - url: "/users/", - responseFilename: responseFileName, - method: .POST - ) - } - - func stubLogOut() { - setupStub( - url: "/users/sign_out", - responseFilename: "LogOutSuccessfully", - method: .DELETE - ) - } - - func stubDeleteAccount() { - setupStub( - url: "/user/delete_account", - responseFilename: "LogOutSuccessfully", - method: .DELETE - ) - } - - func stubLogIn(shouldSucceed: Bool = true) { - let responseFilename = shouldSucceed ? "LoginSuccessfully" : "AuthenticationError" - - setupStub( - url: "/users/sign_in", - responseFilename: responseFilename, - method: .POST - ) - } - - func stubGetProfile() { - setupStub( - url: "/user/profile", - responseFilename: "GetProfileSuccessfully", - method: .GET - ) - } -} diff --git a/ios-baseUITests/Resources/GetProfileSuccessfully.json b/ios-baseUITests/Resources/GetProfileSuccessfully.json deleted file mode 100644 index 681908f8..00000000 --- a/ios-baseUITests/Resources/GetProfileSuccessfully.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "user": { - "id": 1, - "email": "automation@test.com", - "first_name": "FirstName", - "last_name": "LastName", - "username": "test" - } -} diff --git a/ios-baseUITests/Resources/LoginSuccessfully.json b/ios-baseUITests/Resources/LoginSuccessfully.json deleted file mode 100644 index 9185609a..00000000 --- a/ios-baseUITests/Resources/LoginSuccessfully.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "user": { - "id": 1, - "email": "automation@test.com", - "provider": "email", - "uid": "automation@test.com", - "first_name": "FirstName", - "last_name": "LastName", - "username": "test", - "created_at": "2017-02-23T13:54:33.283Z", - "updated_at": "2017-02-23T13:54:33.425Z" - } -} diff --git a/ios-baseUITests/Resources/SignUpSuccessfully.json b/ios-baseUITests/Resources/SignUpSuccessfully.json deleted file mode 100644 index 9185609a..00000000 --- a/ios-baseUITests/Resources/SignUpSuccessfully.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "user": { - "id": 1, - "email": "automation@test.com", - "provider": "email", - "uid": "automation@test.com", - "first_name": "FirstName", - "last_name": "LastName", - "username": "test", - "created_at": "2017-02-23T13:54:33.283Z", - "updated_at": "2017-02-23T13:54:33.425Z" - } -} diff --git a/ios-baseUITests/Resources/AuthenticationError.json b/ios-baseUITests/Resources/StubResponses/AuthenticationError.json similarity index 100% rename from ios-baseUITests/Resources/AuthenticationError.json rename to ios-baseUITests/Resources/StubResponses/AuthenticationError.json diff --git a/ios-baseUITests/Resources/StubResponses/GetProfileFailure.json b/ios-baseUITests/Resources/StubResponses/GetProfileFailure.json new file mode 100644 index 00000000..2bb5158a --- /dev/null +++ b/ios-baseUITests/Resources/StubResponses/GetProfileFailure.json @@ -0,0 +1,3 @@ +{ + "id": null +} diff --git a/ios-baseUITests/Resources/StubResponses/GetProfileSuccessfully.json b/ios-baseUITests/Resources/StubResponses/GetProfileSuccessfully.json new file mode 100644 index 00000000..8a53291c --- /dev/null +++ b/ios-baseUITests/Resources/StubResponses/GetProfileSuccessfully.json @@ -0,0 +1,7 @@ +{ + "id": 1, + "email": "automation@test.com", + "first_name": "FirstName", + "last_name": "LastName", + "username": "test" +} diff --git a/ios-baseUITests/Resources/LogOutSuccessfully.json b/ios-baseUITests/Resources/StubResponses/LogOutSuccessfully.json similarity index 100% rename from ios-baseUITests/Resources/LogOutSuccessfully.json rename to ios-baseUITests/Resources/StubResponses/LogOutSuccessfully.json diff --git a/ios-baseUITests/Resources/StubResponses/LoginSuccessfully.json b/ios-baseUITests/Resources/StubResponses/LoginSuccessfully.json new file mode 100644 index 00000000..7e004c02 --- /dev/null +++ b/ios-baseUITests/Resources/StubResponses/LoginSuccessfully.json @@ -0,0 +1,9 @@ +{ + "id": 1, + "email": "automation@test.com", + "provider": "email", + "uid": "automation@test.com", + "first_name": "FirstName", + "last_name": "LastName", + "username": "test" +} diff --git a/ios-baseUITests/Resources/StubResponses/SignUpSuccessfully.json b/ios-baseUITests/Resources/StubResponses/SignUpSuccessfully.json new file mode 100644 index 00000000..7e004c02 --- /dev/null +++ b/ios-baseUITests/Resources/StubResponses/SignUpSuccessfully.json @@ -0,0 +1,9 @@ +{ + "id": 1, + "email": "automation@test.com", + "provider": "email", + "uid": "automation@test.com", + "first_name": "FirstName", + "last_name": "LastName", + "username": "test" +} diff --git a/ios-baseUITests/XCUIApplicationExtension.swift b/ios-baseUITests/XCUIApplicationExtension.swift index 28d16534..baa60d2b 100644 --- a/ios-baseUITests/XCUIApplicationExtension.swift +++ b/ios-baseUITests/XCUIApplicationExtension.swift @@ -25,7 +25,7 @@ extension XCUIApplication { func logOutIfNeeded(in testCase: XCTestCase) { let logOutButton = buttons["LogoutButton"] let goToSignInButton = buttons["GoToSignInButton"] - + if logOutButton.exists { logOutButton.forceTap() testCase.waitFor(element: goToSignInButton, timeOut: 5) @@ -40,12 +40,12 @@ extension XCUIApplication { let goToSignInButton = buttons["GoToSignInButton"] let toolbarDoneButton = buttons["Toolbar Done Button"] - testCase.waitFor(element: goToSignInButton, timeOut: 2) + testCase.waitFor(element: goToSignInButton, timeOut: 5) goToSignInButton.forceTap() let signInButton = buttons["SignInButton"] - testCase.waitFor(element: signInButton, timeOut: 2) + testCase.waitFor(element: signInButton, timeOut: 5) type(text: email, on: "EmailTextField") @@ -67,7 +67,7 @@ extension XCUIApplication { let toolbarDoneButton = buttons["Toolbar Done Button"] let signUpButton = buttons["SignUpButton"] - testCase.waitFor(element: signUpButton, timeOut: 2) + testCase.waitFor(element: signUpButton, timeOut: 5) type(text: email, on: "EmailTextField") diff --git a/ios-baseUITests/ios_baseUITests.swift b/ios-baseUITests/ios_baseUITests.swift index 8c395de4..6762543c 100644 --- a/ios-baseUITests/ios_baseUITests.swift +++ b/ios-baseUITests/ios_baseUITests.swift @@ -7,20 +7,23 @@ // import XCTest -@testable import ios_base_Debug class ios_baseUITests: XCTestCase { - + var app: XCUIApplication! - let networkMocker = NetworkMocker() + private var networkMocker: NetworkMocker! - override func setUp() { - super.setUp() + override func setUpWithError() throws { + try super.setUpWithError() app = XCUIApplication() app.launchArguments = ["Automation Test"] - - try? networkMocker.setUp() + app.launch() + + networkMocker = NetworkMocker() + try networkMocker.setUp() + networkMocker.stub(with: .logOut, method: .DELETE) + app.logOutIfNeeded(in: self) } override func tearDown() { @@ -29,8 +32,6 @@ class ios_baseUITests: XCTestCase { } func testCreateAccountValidations() { - app.launch() - app.buttons["GoToSignUpButton"].forceTap() let toolbarDoneButton = app.buttons["Toolbar Done Button"] @@ -65,9 +66,7 @@ class ios_baseUITests: XCTestCase { } func testAccountCreation() { - app.launch() - - networkMocker.stubSignUp() + networkMocker.stub(with: .signUp(success: true), method: .POST) app.attemptSignUp( in: self, @@ -75,7 +74,7 @@ class ios_baseUITests: XCTestCase { password: "holahola" ) - networkMocker.stubGetProfile() + networkMocker.stub(with: .profile(success: true), method: .GET) let getMyProfile = app.buttons["GetMyProfileButton"] waitFor(element: getMyProfile, timeOut: 10) getMyProfile.tap() @@ -86,19 +85,10 @@ class ios_baseUITests: XCTestCase { alert.buttons.allElementsBoundByIndex.first?.tap() } - - let logOutButton = app.buttons["LogoutButton"] - waitFor(element: logOutButton, timeOut: 5) - - networkMocker.stubLogOut() - - logOutButton.tap() } func testSignInSuccess() { - app.launch() - - networkMocker.stubLogIn() + networkMocker.stub(with: .signIn(success: true), method: .POST) app.attemptSignIn(in: self, with: "automation@test.com", @@ -106,36 +96,22 @@ class ios_baseUITests: XCTestCase { let logOutButton = app.buttons["LogoutButton"] waitFor(element: logOutButton, timeOut: 10) - - networkMocker.stubLogOut() - logOutButton.forceTap() - - let goToSignInButton = app.buttons["GoToSignInButton"] - waitFor(element: goToSignInButton, timeOut: 10) } func testSignInFailure() { - app.launch() - - networkMocker.stubLogIn(shouldSucceed: false) + networkMocker.stub(with: .signIn(success: false), method: .POST) app.attemptSignIn(in: self, with: "automation@test.com", password: "incorrect password") - if let alert = app.alerts.allElementsBoundByIndex.first { - waitFor(element: alert, timeOut: 2) - - alert.buttons.allElementsBoundByIndex.first?.forceTap() + guard let alert = app.alerts.allElementsBoundByIndex.first else { + return XCTFail("An error alert is expected to appear on Sign In failure") } - - let signInButton = app.buttons["SignInButton"] - waitFor(element: signInButton, timeOut: 2) + alert.buttons.allElementsBoundByIndex.first?.forceTap() } func testSignInValidations() { - app.launch() - app.buttons["GoToSignInButton"].forceTap() let toolbarDoneButton = app.buttons["Toolbar Done Button"] diff --git a/ios-baseUnitTests/Extensions/StringExtensionUnitTests.swift b/ios-baseUnitTests/Cases/Extensions/StringExtensionUnitTests.swift similarity index 95% rename from ios-baseUnitTests/Extensions/StringExtensionUnitTests.swift rename to ios-baseUnitTests/Cases/Extensions/StringExtensionUnitTests.swift index fcaf970d..56bcd94e 100644 --- a/ios-baseUnitTests/Extensions/StringExtensionUnitTests.swift +++ b/ios-baseUnitTests/Cases/Extensions/StringExtensionUnitTests.swift @@ -9,7 +9,7 @@ import XCTest @testable import ios_base_Debug -class StringExtensionUnitTests: XCTestCase { +internal class StringExtensionUnitTests: XCTestCase { func testEmailValidation() { XCTAssertFalse("username".isEmailFormatted()) XCTAssertFalse("username@test".isEmailFormatted()) diff --git a/ios-baseUnitTests/Cases/Managers/SessionManagerTests.swift b/ios-baseUnitTests/Cases/Managers/SessionManagerTests.swift new file mode 100644 index 00000000..b22fd0e5 --- /dev/null +++ b/ios-baseUnitTests/Cases/Managers/SessionManagerTests.swift @@ -0,0 +1,67 @@ +// +// SessionManagerTests.swift +// ios-baseUnitTests +// +// Created by German on 2/9/22. +// Copyright © 2022 Rootstrap Inc. All rights reserved. +// + +import Foundation +import XCTest +@testable import ios_base_Debug + +internal final class SessionManagerTests: XCTestCase { + + private let testSession = Session( + uid: "uuid", + client: "client", + token: "token", + expires: Date() + ) + + private var userDefaults: UserDefaults! + + override func setUp() { + super.setUp() + + // swiftlint:disable:next force_unwrapping + userDefaults = UserDefaults(suiteName: "Test Suite")! + } + + func testSessionStoredCorrectly() { + let sessionManager = SessionManager(userDefaults: userDefaults) + sessionManager.currentSession = testSession + + XCTAssertEqual(testSession.client, sessionManager.currentSession?.client) + XCTAssertEqual(testSession.uid, sessionManager.currentSession?.uid) + XCTAssertEqual(testSession.expiry, sessionManager.currentSession?.expiry) + XCTAssertEqual(testSession.accessToken, sessionManager.currentSession?.accessToken) + } + + func testCurrentUserDeletion() { + let sessionManager = SessionManager(userDefaults: userDefaults) + sessionManager.currentSession = testSession + + XCTAssertNotNil(sessionManager.currentSession) + + sessionManager.deleteSession() + XCTAssertNil(sessionManager.currentSession) + } + + func testSessionValidation() { + let sessionManager = SessionManager(userDefaults: userDefaults) + sessionManager.currentSession = testSession + XCTAssertTrue(sessionManager.validSession) + + let invalidSessions: [Session] = [ + .init(uid: "", client: "client", token: "token", expires: Date()), + .init(uid: "uid", client: "", token: "token", expires: nil), + .init(uid: "uid", client: "client", token: "", expires: Date.distantFuture), + .init(uid: nil, client: nil, token: "accessToken", expires: Date.distantFuture) + ] + for session in invalidSessions { + sessionManager.currentSession = session + XCTAssertFalse(sessionManager.validSession) + } + } +} diff --git a/ios-baseUnitTests/Cases/Managers/UserDataManagerTests.swift b/ios-baseUnitTests/Cases/Managers/UserDataManagerTests.swift new file mode 100644 index 00000000..74600f0c --- /dev/null +++ b/ios-baseUnitTests/Cases/Managers/UserDataManagerTests.swift @@ -0,0 +1,36 @@ +// +// UserDataManagerTests.swift +// ios-baseUnitTests +// +// Created by German on 1/9/22. +// Copyright © 2022 Rootstrap Inc. All rights reserved. +// + +import Foundation +import XCTest +@testable import ios_base_Debug + +internal final class UserDataManagerTests: XCTestCase { + + func testUserPersistedCorrectly() { + let testUser: User = .mock + UserDataManager.currentUser = testUser + + let persistedUser = UserDataManager.currentUser + XCTAssertEqual(persistedUser?.id, testUser.id) + XCTAssertEqual(persistedUser?.username, testUser.username) + XCTAssertEqual(persistedUser?.email, testUser.email) + XCTAssertTrue(UserDataManager.isUserLogged) + } + + func testCurrentUserDeletion() { + UserDataManager.currentUser = .mock + + XCTAssertNotNil(UserDataManager.currentUser) + XCTAssertTrue(UserDataManager.isUserLogged) + + UserDataManager.deleteUser() + XCTAssertNil(UserDataManager.currentUser) + XCTAssertFalse(UserDataManager.isUserLogged) + } +} diff --git a/ios-baseUnitTests/Cases/Services/AuthenticationServicesTests.swift b/ios-baseUnitTests/Cases/Services/AuthenticationServicesTests.swift new file mode 100644 index 00000000..b03630ae --- /dev/null +++ b/ios-baseUnitTests/Cases/Services/AuthenticationServicesTests.swift @@ -0,0 +1,16 @@ +// +// AuthenticationServicesTests.swift +// ios-baseUnitTests +// +// Created by German on 1/9/22. +// Copyright © 2022 Rootstrap Inc. All rights reserved. +// + +import Foundation +import XCTest + +internal final class AuthenticationServicesTests: XCTestCase { + func testAuthenticationService() { + + } +} diff --git a/ios-baseUnitTests/Cases/Services/UserServiceUnitTests.swift b/ios-baseUnitTests/Cases/Services/UserServiceUnitTests.swift new file mode 100644 index 00000000..ba9d6b4b --- /dev/null +++ b/ios-baseUnitTests/Cases/Services/UserServiceUnitTests.swift @@ -0,0 +1,72 @@ +// +// UserServiceUnitTests.swift +// ios-baseUnitTests +// +// Created by German on 4/30/20. +// Copyright © 2020 Rootstrap Inc. All rights reserved. +// + +import XCTest +import RSSwiftNetworking +@testable import ios_base_Debug + +class UserServiceUnitTests: XCTestCase { + + private var userService: UserServices! + private var networkMocker: NetworkMocker! + + override func setUpWithError() throws { + try super.setUpWithError() + + networkMocker = NetworkMocker() + try networkMocker.setUp() + userService = UserServices() + UserDataManager.deleteUser() + } + + override func tearDown() { + super.tearDown() + networkMocker.tearDown() + } + + func testServiceSavesUser() { + let expectation = expectation(description: "User object should be persisted") + testUserStorageAfterProfileFetch(success: true) { result in + switch result { + case .success(let user): + expectation.fulfill() + XCTAssertNotNil(UserDataManager.currentUser) + XCTAssertEqual(UserDataManager.currentUser?.id, user.id) + XCTAssertEqual(UserDataManager.currentUser?.email, user.email) + case .failure(let error): + XCTFail("Test expected to succeed. \(error)") + } + } + + wait(for: [expectation], timeout: 5.0) + } + + func testServicesReturnsError() { + let expectation = expectation(description: "Request should fail") + testUserStorageAfterProfileFetch(success: false) { result in + switch result { + case .success: + XCTFail("GET Profile Request expected to fail") + case .failure(let error): + expectation.fulfill() + XCTAssertNil(UserDataManager.currentUser) + XCTAssertNotNil(error) + } + + } + wait(for: [expectation], timeout: 5.0) + } + + private func testUserStorageAfterProfileFetch( + success: Bool, + completion: @escaping (Result) -> Void + ) { + networkMocker.stub(with: .profile(success: success), method: .GET) + userService.getMyProfile(completion: completion) + } +} diff --git a/ios-baseUnitTests/Extensions/User+Tests.swift b/ios-baseUnitTests/Extensions/User+Tests.swift new file mode 100644 index 00000000..1a0429f5 --- /dev/null +++ b/ios-baseUnitTests/Extensions/User+Tests.swift @@ -0,0 +1,14 @@ +// +// User+Tests.swift +// ios-baseUnitTests +// +// Created by German on 2/9/22. +// Copyright © 2022 Rootstrap Inc. All rights reserved. +// + +import Foundation +@testable import ios_base_Debug + +internal extension User { + static let mock = User(id: 1, username: "tester", email: "tester@rootstrap.com") +} diff --git a/ios-baseUnitTests/Services/UserServiceUnitTests.swift b/ios-baseUnitTests/Services/UserServiceUnitTests.swift deleted file mode 100644 index 0991c288..00000000 --- a/ios-baseUnitTests/Services/UserServiceUnitTests.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// UserServiceUnitTests.swift -// ios-baseUnitTests -// -// Created by German on 4/30/20. -// Copyright © 2020 Rootstrap Inc. All rights reserved. -// - -import XCTest -@testable import ios_base_Debug - -class UserServiceUnitTests: XCTestCase { - - let userResponse: [String: Any] = [ - "user": [ - "id": 0, - "username": "test", - "email": "test-user@rootstrap.com" - ] - ] - - var testUser: User! - - override func setUp() { - super.setUp() - if let userDictionary = userResponse["user"] as? [String: Any] { - testUser = User(dictionary: userDictionary) - } - SessionManager.deleteSession() - UserDataManager.deleteUser() - } - - func testUserPersistence() { - AuthenticationServices.saveUserSession(fromResponse: userResponse, headers: [:]) - guard let persistedUser = UserDataManager.currentUser else { - XCTFail("User should NOT be nil") - return - } - XCTAssert(UserDataManager.isUserLogged) - XCTAssert(persistedUser.id == testUser.id) - XCTAssert(persistedUser.username == testUser.username) - XCTAssert(persistedUser.email == testUser.email) - } - - func testGoodSessionPersistence() { - let client = "dummySessionClient" - let token = "dummySessionToken" - let uid = testUser.email - let expiry = "\(Date.timeIntervalSinceReferenceDate)" - let sessionHeaders: [String: Any] = [ - APIClient.HTTPHeader.uid.rawValue: uid, - APIClient.HTTPHeader.client.rawValue: client, - APIClient.HTTPHeader.token.rawValue: token, - APIClient.HTTPHeader.expiry.rawValue: expiry - ] - - AuthenticationServices.saveUserSession( - fromResponse: userResponse, - headers: sessionHeaders - ) - guard let persistedSession = SessionManager.currentSession else { - XCTFail("Session should NOT be nil") - return - } - - XCTAssert(persistedSession.client == client) - XCTAssert(persistedSession.accessToken == token) - XCTAssert(persistedSession.uid == uid) - XCTAssert(persistedSession.accessToken == token) - } - - func testBadSessionPersistence() { - // Testing case where shouldn't be session at all - let unusableHeaders = [APIClient.HTTPHeader.client: "badHeaderKey"] - AuthenticationServices.saveUserSession( - fromResponse: userResponse, - headers: unusableHeaders - ) - XCTAssert(SessionManager.currentSession == nil) - XCTAssertFalse(SessionManager.validSession) - - // Testing case where should be session but not valid - let wrongSessionHeaders = [ - "testKey": "testValue", - APIClient.HTTPHeader.uid.rawValue: "", - APIClient.HTTPHeader.client.rawValue: "", - APIClient.HTTPHeader.token.rawValue: "" - ] - AuthenticationServices.saveUserSession( - fromResponse: userResponse, - headers: wrongSessionHeaders - ) - XCTAssert(SessionManager.currentSession != nil) - XCTAssertFalse(SessionManager.validSession) - } -}