diff --git a/.swiftlint.yml b/.swiftlint.yml index 92669d7aa..b6dcc96e7 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -20,6 +20,7 @@ excluded: # paths to ignore during linting. Takes precedence over `included`. - Carthage - DerivedData - Pods + - DerivedData - Core/CoreTests - Authorization/AuthorizationTests - Course/CourseTests @@ -30,6 +31,7 @@ excluded: # paths to ignore during linting. Takes precedence over `included`. - Profile/ProfileTests - WhatsNew/WhatsNewTests - Theme/ThemeTests + - AppDates/AppDatesTests - vendor - Core/Core/SwiftGen - Authorization/Authorization/SwiftGen @@ -41,6 +43,7 @@ excluded: # paths to ignore during linting. Takes precedence over `included`. - Profile/Profile/SwiftGen - WhatsNew/WhatsNew/SwiftGen - Theme/Theme/SwiftGen + - AppDates/AppDates/SwiftGen # - Source/ExcludedFile.swift # - Source/*/ExcludedFile.swift # Exclude files with a wildcard #analyzer_rules: # Rules run by `swiftlint analyze` (experimental) diff --git a/AppDates/.gitignore b/AppDates/.gitignore new file mode 100644 index 000000000..57346ee26 --- /dev/null +++ b/AppDates/.gitignore @@ -0,0 +1,99 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/* +/AppDates.xcodeproj/xcuserdata/ +/AppDates.xcworkspace/xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## R.swift +R.generated.swift + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +.DS_Store +.idea +xcode-frameworks diff --git a/AppDates/AppDates.xcodeproj/project.pbxproj b/AppDates/AppDates.xcodeproj/project.pbxproj new file mode 100644 index 000000000..0438af748 --- /dev/null +++ b/AppDates/AppDates.xcodeproj/project.pbxproj @@ -0,0 +1,1628 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 63; + objects = { + +/* Begin PBXBuildFile section */ + 9941863792906D15DF2A3ECA /* Pods_App_AppDates.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E2984D28B27605D024FE0AC /* Pods_App_AppDates.framework */; }; + A1A9C1B7F354E7604855D612 /* Pods_App_AppDates_AppDatesTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8CE12953BBB7F7C65675973B /* Pods_App_AppDates_AppDatesTests.framework */; }; + CE75F3AE2D9A89AA0024BB43 /* AppDatesMock.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE75F3AD2D9A89AA0024BB43 /* AppDatesMock.generated.swift */; }; + CE815A672D60D75200CB9114 /* AppDates.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE815A5E2D60D75200CB9114 /* AppDates.framework */; }; + CE815A792D60D78B00CB9114 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE815A782D60D78B00CB9114 /* Core.framework */; }; + CE815A7E2D60D7CC00CB9114 /* OEXFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = CE815A7D2D60D7CC00CB9114 /* OEXFoundation */; }; + CE815A9C2D60DB8A00CB9114 /* OEXFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = CE815A9B2D60DB8A00CB9114 /* OEXFoundation */; }; + CE815AA02D60DCCB00CB9114 /* swiftgen.yml in Resources */ = {isa = PBXBuildFile; fileRef = CE815A9F2D60DCCB00CB9114 /* swiftgen.yml */; }; + CE815AA32D60DD6B00CB9114 /* AppDatesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE815AA12D60DD6B00CB9114 /* AppDatesTests.swift */; }; + CE8649A22DA57A1C00821EC9 /* Data_Dates.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8649852DA57A1C00821EC9 /* Data_Dates.swift */; }; + CE8649A32DA57A1C00821EC9 /* DatesEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8649872DA57A1C00821EC9 /* DatesEndpoint.swift */; }; + CE8649A42DA57A1C00821EC9 /* DatesCoreModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CE8649892DA57A1C00821EC9 /* DatesCoreModel.xcdatamodeld */; }; + CE8649A52DA57A1C00821EC9 /* DatesPersistenceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE86498B2DA57A1C00821EC9 /* DatesPersistenceProtocol.swift */; }; + CE8649A62DA57A1C00821EC9 /* DatesRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE86498C2DA57A1C00821EC9 /* DatesRepository.swift */; }; + CE8649A72DA57A1C00821EC9 /* CourseDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE86498E2DA57A1C00821EC9 /* CourseDate.swift */; }; + CE8649A82DA57A1C00821EC9 /* DateGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE86498F2DA57A1C00821EC9 /* DateGroup.swift */; }; + CE8649A92DA57A1C00821EC9 /* DateGroupType.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8649902DA57A1C00821EC9 /* DateGroupType.swift */; }; + CE8649AA2DA57A1C00821EC9 /* DatesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8649912DA57A1C00821EC9 /* DatesInteractor.swift */; }; + CE8649AB2DA57A1C00821EC9 /* DateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8649932DA57A1C00821EC9 /* DateCell.swift */; }; + CE8649AC2DA57A1C00821EC9 /* DatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8649942DA57A1C00821EC9 /* DatesView.swift */; }; + CE8649AD2DA57A1C00821EC9 /* DatesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8649952DA57A1C00821EC9 /* DatesViewModel.swift */; }; + CE8649AE2DA57A1C00821EC9 /* ShiftDueDatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8649962DA57A1C00821EC9 /* ShiftDueDatesView.swift */; }; + CE8649AF2DA57A1C00821EC9 /* AppDatesAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8649982DA57A1C00821EC9 /* AppDatesAnalytics.swift */; }; + CE8649B02DA57A1C00821EC9 /* AppDatesRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8649992DA57A1C00821EC9 /* AppDatesRouter.swift */; }; + CE8649B12DA57A1C00821EC9 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE86499B2DA57A1C00821EC9 /* Strings.swift */; }; + CE8649B22DA57A1C00821EC9 /* AppDates.h in Headers */ = {isa = PBXBuildFile; fileRef = CE86499D2DA57A1C00821EC9 /* AppDates.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CE8649B32DA57A1C00821EC9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CE86499F2DA57A1C00821EC9 /* Localizable.strings */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + CE815A682D60D75200CB9114 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CE815A552D60D75200CB9114 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CE815A5D2D60D75200CB9114; + remoteInfo = AppDates; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + CE815A9E2D60DB8A00CB9114 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 123278802021B24B608B2BBC /* Pods-App-AppDates.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates.releasedev.xcconfig"; path = "Target Support Files/Pods-App-AppDates/Pods-App-AppDates.releasedev.xcconfig"; sourceTree = ""; }; + 14161693326A93866BB01A36 /* Pods-App-AppDates-AppDatesTests.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates-AppDatesTests.releasestage.xcconfig"; path = "Target Support Files/Pods-App-AppDates-AppDatesTests/Pods-App-AppDates-AppDatesTests.releasestage.xcconfig"; sourceTree = ""; }; + 200A76876806F62C2118F49E /* Pods-App-AppDates-AppDatesTests.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates-AppDatesTests.debugdev.xcconfig"; path = "Target Support Files/Pods-App-AppDates-AppDatesTests/Pods-App-AppDates-AppDatesTests.debugdev.xcconfig"; sourceTree = ""; }; + 2EF1216388E705D2C6F9D531 /* Pods-App-AppDates-AppDatesTests.debugstage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates-AppDatesTests.debugstage.xcconfig"; path = "Target Support Files/Pods-App-AppDates-AppDatesTests/Pods-App-AppDates-AppDatesTests.debugstage.xcconfig"; sourceTree = ""; }; + 3A7BB333BEA868D5F232238B /* Pods-App-AppDates-AppDatesTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates-AppDatesTests.debug.xcconfig"; path = "Target Support Files/Pods-App-AppDates-AppDatesTests/Pods-App-AppDates-AppDatesTests.debug.xcconfig"; sourceTree = ""; }; + 530623E65DE5652AD8E95ABC /* Pods-App-AppDates-AppDatesTests.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates-AppDatesTests.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-AppDates-AppDatesTests/Pods-App-AppDates-AppDatesTests.releaseprod.xcconfig"; sourceTree = ""; }; + 73A3196DAD0788A707947697 /* Pods-App-AppDates-AppDatesTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates-AppDatesTests.release.xcconfig"; path = "Target Support Files/Pods-App-AppDates-AppDatesTests/Pods-App-AppDates-AppDatesTests.release.xcconfig"; sourceTree = ""; }; + 74B0926D150970FC4F6AD7E8 /* Pods-App-AppDates.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates.debugprod.xcconfig"; path = "Target Support Files/Pods-App-AppDates/Pods-App-AppDates.debugprod.xcconfig"; sourceTree = ""; }; + 7F010B137D29C820343761A9 /* Pods-App-AppDates.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates.debugdev.xcconfig"; path = "Target Support Files/Pods-App-AppDates/Pods-App-AppDates.debugdev.xcconfig"; sourceTree = ""; }; + 8CE12953BBB7F7C65675973B /* Pods_App_AppDates_AppDatesTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_AppDates_AppDatesTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9E2984D28B27605D024FE0AC /* Pods_App_AppDates.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_AppDates.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A3F5B654743233E0EBF20ACE /* Pods-App-AppDates.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates.debug.xcconfig"; path = "Target Support Files/Pods-App-AppDates/Pods-App-AppDates.debug.xcconfig"; sourceTree = ""; }; + A4FF28047F07A8E553DD3C3F /* Pods-App-AppDates.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-AppDates/Pods-App-AppDates.releaseprod.xcconfig"; sourceTree = ""; }; + AFC846633FC6ED95F9884DE0 /* Pods-App-AppDates-AppDatesTests.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates-AppDatesTests.releasedev.xcconfig"; path = "Target Support Files/Pods-App-AppDates-AppDatesTests/Pods-App-AppDates-AppDatesTests.releasedev.xcconfig"; sourceTree = ""; }; + B3A97A12BDD649309E86E47F /* Pods-App-AppDates.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates.releasestage.xcconfig"; path = "Target Support Files/Pods-App-AppDates/Pods-App-AppDates.releasestage.xcconfig"; sourceTree = ""; }; + B425DC0B44831EA155630C1D /* Pods-App-AppDates.debugstage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates.debugstage.xcconfig"; path = "Target Support Files/Pods-App-AppDates/Pods-App-AppDates.debugstage.xcconfig"; sourceTree = ""; }; + CC47B8A74F999FBDA6DC2597 /* Pods-App-AppDates-AppDatesTests.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates-AppDatesTests.debugprod.xcconfig"; path = "Target Support Files/Pods-App-AppDates-AppDatesTests/Pods-App-AppDates-AppDatesTests.debugprod.xcconfig"; sourceTree = ""; }; + CE75F3AD2D9A89AA0024BB43 /* AppDatesMock.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDatesMock.generated.swift; sourceTree = ""; }; + CE815A5E2D60D75200CB9114 /* AppDates.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppDates.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CE815A662D60D75200CB9114 /* AppDatesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppDatesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + CE815A782D60D78B00CB9114 /* Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Core.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CE815A9F2D60DCCB00CB9114 /* swiftgen.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = swiftgen.yml; sourceTree = ""; }; + CE815AA12D60DD6B00CB9114 /* AppDatesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDatesTests.swift; sourceTree = ""; }; + CE8649852DA57A1C00821EC9 /* Data_Dates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data_Dates.swift; sourceTree = ""; }; + CE8649872DA57A1C00821EC9 /* DatesEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatesEndpoint.swift; sourceTree = ""; }; + CE86498B2DA57A1C00821EC9 /* DatesPersistenceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatesPersistenceProtocol.swift; sourceTree = ""; }; + CE86498C2DA57A1C00821EC9 /* DatesRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatesRepository.swift; sourceTree = ""; }; + CE86498E2DA57A1C00821EC9 /* CourseDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDate.swift; sourceTree = ""; }; + CE86498F2DA57A1C00821EC9 /* DateGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateGroup.swift; sourceTree = ""; }; + CE8649902DA57A1C00821EC9 /* DateGroupType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateGroupType.swift; sourceTree = ""; }; + CE8649912DA57A1C00821EC9 /* DatesInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatesInteractor.swift; sourceTree = ""; }; + CE8649932DA57A1C00821EC9 /* DateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateCell.swift; sourceTree = ""; }; + CE8649942DA57A1C00821EC9 /* DatesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatesView.swift; sourceTree = ""; }; + CE8649952DA57A1C00821EC9 /* DatesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatesViewModel.swift; sourceTree = ""; }; + CE8649962DA57A1C00821EC9 /* ShiftDueDatesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShiftDueDatesView.swift; sourceTree = ""; }; + CE8649982DA57A1C00821EC9 /* AppDatesAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDatesAnalytics.swift; sourceTree = ""; }; + CE8649992DA57A1C00821EC9 /* AppDatesRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDatesRouter.swift; sourceTree = ""; }; + CE86499B2DA57A1C00821EC9 /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; + CE86499D2DA57A1C00821EC9 /* AppDates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDates.h; sourceTree = ""; }; + CE86499E2DA57A1C00821EC9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + CE8649A12DA57A1C00821EC9 /* DatesCoreModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DatesCoreModel.xcdatamodel; sourceTree = ""; }; + ED203AA9F3F382E5A5779E5F /* Pods-App-AppDates.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-AppDates.release.xcconfig"; path = "Target Support Files/Pods-App-AppDates/Pods-App-AppDates.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + CE815A5B2D60D75200CB9114 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CE815A7E2D60D7CC00CB9114 /* OEXFoundation in Frameworks */, + CE815A792D60D78B00CB9114 /* Core.framework in Frameworks */, + 9941863792906D15DF2A3ECA /* Pods_App_AppDates.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CE815A632D60D75200CB9114 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CE815A672D60D75200CB9114 /* AppDates.framework in Frameworks */, + CE815A9C2D60DB8A00CB9114 /* OEXFoundation in Frameworks */, + A1A9C1B7F354E7604855D612 /* Pods_App_AppDates_AppDatesTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 7100E08FEDE0D748CBD6D05B /* Pods */ = { + isa = PBXGroup; + children = ( + A3F5B654743233E0EBF20ACE /* Pods-App-AppDates.debug.xcconfig */, + 74B0926D150970FC4F6AD7E8 /* Pods-App-AppDates.debugprod.xcconfig */, + B425DC0B44831EA155630C1D /* Pods-App-AppDates.debugstage.xcconfig */, + 7F010B137D29C820343761A9 /* Pods-App-AppDates.debugdev.xcconfig */, + ED203AA9F3F382E5A5779E5F /* Pods-App-AppDates.release.xcconfig */, + B3A97A12BDD649309E86E47F /* Pods-App-AppDates.releasestage.xcconfig */, + A4FF28047F07A8E553DD3C3F /* Pods-App-AppDates.releaseprod.xcconfig */, + 123278802021B24B608B2BBC /* Pods-App-AppDates.releasedev.xcconfig */, + 3A7BB333BEA868D5F232238B /* Pods-App-AppDates-AppDatesTests.debug.xcconfig */, + CC47B8A74F999FBDA6DC2597 /* Pods-App-AppDates-AppDatesTests.debugprod.xcconfig */, + 2EF1216388E705D2C6F9D531 /* Pods-App-AppDates-AppDatesTests.debugstage.xcconfig */, + 200A76876806F62C2118F49E /* Pods-App-AppDates-AppDatesTests.debugdev.xcconfig */, + 73A3196DAD0788A707947697 /* Pods-App-AppDates-AppDatesTests.release.xcconfig */, + 14161693326A93866BB01A36 /* Pods-App-AppDates-AppDatesTests.releasestage.xcconfig */, + 530623E65DE5652AD8E95ABC /* Pods-App-AppDates-AppDatesTests.releaseprod.xcconfig */, + AFC846633FC6ED95F9884DE0 /* Pods-App-AppDates-AppDatesTests.releasedev.xcconfig */, + ); + name = Pods; + path = ../Pods; + sourceTree = ""; + }; + CE815A542D60D75200CB9114 = { + isa = PBXGroup; + children = ( + CE815A9F2D60DCCB00CB9114 /* swiftgen.yml */, + CE8649A02DA57A1C00821EC9 /* AppDates */, + CE815AA22D60DD6B00CB9114 /* AppDatesTests */, + CE815A772D60D78B00CB9114 /* Frameworks */, + CE815A5F2D60D75200CB9114 /* Products */, + 7100E08FEDE0D748CBD6D05B /* Pods */, + ); + sourceTree = ""; + }; + CE815A5F2D60D75200CB9114 /* Products */ = { + isa = PBXGroup; + children = ( + CE815A5E2D60D75200CB9114 /* AppDates.framework */, + CE815A662D60D75200CB9114 /* AppDatesTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + CE815A772D60D78B00CB9114 /* Frameworks */ = { + isa = PBXGroup; + children = ( + CE815A782D60D78B00CB9114 /* Core.framework */, + 9E2984D28B27605D024FE0AC /* Pods_App_AppDates.framework */, + 8CE12953BBB7F7C65675973B /* Pods_App_AppDates_AppDatesTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + CE815AA22D60DD6B00CB9114 /* AppDatesTests */ = { + isa = PBXGroup; + children = ( + CE815AA12D60DD6B00CB9114 /* AppDatesTests.swift */, + CE75F3AD2D9A89AA0024BB43 /* AppDatesMock.generated.swift */, + ); + path = AppDatesTests; + sourceTree = ""; + }; + CE8649862DA57A1C00821EC9 /* Model */ = { + isa = PBXGroup; + children = ( + CE8649852DA57A1C00821EC9 /* Data_Dates.swift */, + ); + path = Model; + sourceTree = ""; + }; + CE8649882DA57A1C00821EC9 /* Network */ = { + isa = PBXGroup; + children = ( + CE8649872DA57A1C00821EC9 /* DatesEndpoint.swift */, + ); + path = Network; + sourceTree = ""; + }; + CE86498A2DA57A1C00821EC9 /* Persistence */ = { + isa = PBXGroup; + children = ( + CE8649892DA57A1C00821EC9 /* DatesCoreModel.xcdatamodeld */, + ); + path = Persistence; + sourceTree = ""; + }; + CE86498D2DA57A1C00821EC9 /* Data */ = { + isa = PBXGroup; + children = ( + CE8649862DA57A1C00821EC9 /* Model */, + CE8649882DA57A1C00821EC9 /* Network */, + CE86498A2DA57A1C00821EC9 /* Persistence */, + CE86498B2DA57A1C00821EC9 /* DatesPersistenceProtocol.swift */, + CE86498C2DA57A1C00821EC9 /* DatesRepository.swift */, + ); + path = Data; + sourceTree = ""; + }; + CE8649922DA57A1C00821EC9 /* Domain */ = { + isa = PBXGroup; + children = ( + CE86498E2DA57A1C00821EC9 /* CourseDate.swift */, + CE86498F2DA57A1C00821EC9 /* DateGroup.swift */, + CE8649902DA57A1C00821EC9 /* DateGroupType.swift */, + CE8649912DA57A1C00821EC9 /* DatesInteractor.swift */, + ); + path = Domain; + sourceTree = ""; + }; + CE8649972DA57A1C00821EC9 /* Dates */ = { + isa = PBXGroup; + children = ( + CE8649932DA57A1C00821EC9 /* DateCell.swift */, + CE8649942DA57A1C00821EC9 /* DatesView.swift */, + CE8649952DA57A1C00821EC9 /* DatesViewModel.swift */, + CE8649962DA57A1C00821EC9 /* ShiftDueDatesView.swift */, + ); + path = Dates; + sourceTree = ""; + }; + CE86499A2DA57A1C00821EC9 /* Presentation */ = { + isa = PBXGroup; + children = ( + CE8649972DA57A1C00821EC9 /* Dates */, + CE8649982DA57A1C00821EC9 /* AppDatesAnalytics.swift */, + CE8649992DA57A1C00821EC9 /* AppDatesRouter.swift */, + ); + path = Presentation; + sourceTree = ""; + }; + CE86499C2DA57A1C00821EC9 /* SwiftGen */ = { + isa = PBXGroup; + children = ( + CE86499B2DA57A1C00821EC9 /* Strings.swift */, + ); + path = SwiftGen; + sourceTree = ""; + }; + CE8649A02DA57A1C00821EC9 /* AppDates */ = { + isa = PBXGroup; + children = ( + CE86498D2DA57A1C00821EC9 /* Data */, + CE8649922DA57A1C00821EC9 /* Domain */, + CE86499A2DA57A1C00821EC9 /* Presentation */, + CE86499C2DA57A1C00821EC9 /* SwiftGen */, + CE86499D2DA57A1C00821EC9 /* AppDates.h */, + CE86499F2DA57A1C00821EC9 /* Localizable.strings */, + ); + path = AppDates; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + CE815A592D60D75200CB9114 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + CE8649B22DA57A1C00821EC9 /* AppDates.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + CE815A5D2D60D75200CB9114 /* AppDates */ = { + isa = PBXNativeTarget; + buildConfigurationList = CE815A6F2D60D75200CB9114 /* Build configuration list for PBXNativeTarget "AppDates" */; + buildPhases = ( + 759B7F680F2C43302B30CF60 /* [CP] Check Pods Manifest.lock */, + CE815A592D60D75200CB9114 /* Headers */, + CE815AAF2D60DE5E00CB9114 /* SwiftGen */, + CE815A5A2D60D75200CB9114 /* Sources */, + CE815A5B2D60D75200CB9114 /* Frameworks */, + CE815A5C2D60D75200CB9114 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AppDates; + packageProductDependencies = ( + CE815A7D2D60D7CC00CB9114 /* OEXFoundation */, + ); + productName = AppDates; + productReference = CE815A5E2D60D75200CB9114 /* AppDates.framework */; + productType = "com.apple.product-type.framework"; + }; + CE815A652D60D75200CB9114 /* AppDatesTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = CE815A742D60D75200CB9114 /* Build configuration list for PBXNativeTarget "AppDatesTests" */; + buildPhases = ( + 111485BE8CEAEA30E412D96C /* [CP] Check Pods Manifest.lock */, + CE815A622D60D75200CB9114 /* Sources */, + CE815A632D60D75200CB9114 /* Frameworks */, + CE815A642D60D75200CB9114 /* Resources */, + F2D2F6B58295FDBB32EB70FE /* [CP] Copy Pods Resources */, + CE815A9E2D60DB8A00CB9114 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + CE815A692D60D75200CB9114 /* PBXTargetDependency */, + ); + name = AppDatesTests; + productName = AppDatesTests; + productReference = CE815A662D60D75200CB9114 /* AppDatesTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + CE815A552D60D75200CB9114 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1620; + LastUpgradeCheck = 1620; + TargetAttributes = { + CE815A5D2D60D75200CB9114 = { + CreatedOnToolsVersion = 16.2; + LastSwiftMigration = 1620; + }; + CE815A652D60D75200CB9114 = { + CreatedOnToolsVersion = 16.2; + }; + }; + }; + buildConfigurationList = CE815A582D60D75200CB9114 /* Build configuration list for PBXProject "AppDates" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = CE815A542D60D75200CB9114; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + CE815A7C2D60D7CC00CB9114 /* XCRemoteSwiftPackageReference "openedx-app-foundation-ios" */, + ); + productRefGroup = CE815A5F2D60D75200CB9114 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + CE815A5D2D60D75200CB9114 /* AppDates */, + CE815A652D60D75200CB9114 /* AppDatesTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + CE815A5C2D60D75200CB9114 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CE815AA02D60DCCB00CB9114 /* swiftgen.yml in Resources */, + CE8649B32DA57A1C00821EC9 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CE815A642D60D75200CB9114 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 111485BE8CEAEA30E412D96C /* [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-App-AppDates-AppDatesTests-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; + }; + 759B7F680F2C43302B30CF60 /* [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-App-AppDates-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; + }; + CE815AAF2D60DE5E00CB9114 /* SwiftGen */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = SwiftGen; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [[ -f \"${PODS_ROOT}/SwiftGen/bin/swiftgen\" ]]; then\n \"${PODS_ROOT}/SwiftGen/bin/swiftgen\"\nelse\n echo \"warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it.\"\nfi\n"; + }; + F2D2F6B58295FDBB32EB70FE /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-App-AppDates-AppDatesTests/Pods-App-AppDates-AppDatesTests-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-App-AppDates-AppDatesTests/Pods-App-AppDates-AppDatesTests-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App-AppDates-AppDatesTests/Pods-App-AppDates-AppDatesTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + CE815A5A2D60D75200CB9114 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CE8649A22DA57A1C00821EC9 /* Data_Dates.swift in Sources */, + CE8649A32DA57A1C00821EC9 /* DatesEndpoint.swift in Sources */, + CE8649A42DA57A1C00821EC9 /* DatesCoreModel.xcdatamodeld in Sources */, + CE8649A52DA57A1C00821EC9 /* DatesPersistenceProtocol.swift in Sources */, + CE8649A62DA57A1C00821EC9 /* DatesRepository.swift in Sources */, + CE8649A72DA57A1C00821EC9 /* CourseDate.swift in Sources */, + CE8649A82DA57A1C00821EC9 /* DateGroup.swift in Sources */, + CE8649A92DA57A1C00821EC9 /* DateGroupType.swift in Sources */, + CE8649AA2DA57A1C00821EC9 /* DatesInteractor.swift in Sources */, + CE8649AB2DA57A1C00821EC9 /* DateCell.swift in Sources */, + CE8649AC2DA57A1C00821EC9 /* DatesView.swift in Sources */, + CE8649AD2DA57A1C00821EC9 /* DatesViewModel.swift in Sources */, + CE8649AE2DA57A1C00821EC9 /* ShiftDueDatesView.swift in Sources */, + CE8649AF2DA57A1C00821EC9 /* AppDatesAnalytics.swift in Sources */, + CE8649B02DA57A1C00821EC9 /* AppDatesRouter.swift in Sources */, + CE8649B12DA57A1C00821EC9 /* Strings.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CE815A622D60D75200CB9114 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CE75F3AE2D9A89AA0024BB43 /* AppDatesMock.generated.swift in Sources */, + CE815AA32D60DD6B00CB9114 /* AppDatesTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + CE815A692D60D75200CB9114 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = CE815A5D2D60D75200CB9114 /* AppDates */; + targetProxy = CE815A682D60D75200CB9114 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + CE86499F2DA57A1C00821EC9 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + CE86499E2DA57A1C00821EC9 /* en */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + CE815A702D60D75200CB9114 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3F5B654743233E0EBF20ACE /* Pods-App-AppDates.debug.xcconfig */; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AppDates; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + CE815A712D60D75200CB9114 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ED203AA9F3F382E5A5779E5F /* Pods-App-AppDates.release.xcconfig */; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AppDates; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + CE815A722D60D75200CB9114 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + CE815A732D60D75200CB9114 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + CE815A752D60D75200CB9114 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3A7BB333BEA868D5F232238B /* Pods-App-AppDates-AppDatesTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MACOSX_DEPLOYMENT_TARGET = 12.6; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = stepanok.com.AppDatesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + CE815A762D60D75200CB9114 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 73A3196DAD0788A707947697 /* Pods-App-AppDates-AppDatesTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MACOSX_DEPLOYMENT_TARGET = 12.6; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = stepanok.com.AppDatesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + CE815A822D60D84C00CB9114 /* DebugDev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugDev; + }; + CE815A832D60D84C00CB9114 /* DebugDev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7F010B137D29C820343761A9 /* Pods-App-AppDates.debugdev.xcconfig */; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AppDates; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugDev; + }; + CE815A842D60D84C00CB9114 /* DebugDev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 200A76876806F62C2118F49E /* Pods-App-AppDates-AppDatesTests.debugdev.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MACOSX_DEPLOYMENT_TARGET = 12.6; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = stepanok.com.AppDatesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugDev; + }; + CE815A852D60D85400CB9114 /* DebugStage */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugStage; + }; + CE815A862D60D85400CB9114 /* DebugStage */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B425DC0B44831EA155630C1D /* Pods-App-AppDates.debugstage.xcconfig */; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AppDates; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugStage; + }; + CE815A872D60D85400CB9114 /* DebugStage */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2EF1216388E705D2C6F9D531 /* Pods-App-AppDates-AppDatesTests.debugstage.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MACOSX_DEPLOYMENT_TARGET = 12.6; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = stepanok.com.AppDatesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugStage; + }; + CE815A882D60D85B00CB9114 /* DebugProd */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugProd; + }; + CE815A892D60D85B00CB9114 /* DebugProd */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 74B0926D150970FC4F6AD7E8 /* Pods-App-AppDates.debugprod.xcconfig */; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AppDates; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugProd; + }; + CE815A8A2D60D85B00CB9114 /* DebugProd */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CC47B8A74F999FBDA6DC2597 /* Pods-App-AppDates-AppDatesTests.debugprod.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MACOSX_DEPLOYMENT_TARGET = 12.6; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = stepanok.com.AppDatesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugProd; + }; + CE815A8B2D60D86300CB9114 /* ReleaseDev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseDev; + }; + CE815A8C2D60D86300CB9114 /* ReleaseDev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 123278802021B24B608B2BBC /* Pods-App-AppDates.releasedev.xcconfig */; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AppDates; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseDev; + }; + CE815A8D2D60D86300CB9114 /* ReleaseDev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AFC846633FC6ED95F9884DE0 /* Pods-App-AppDates-AppDatesTests.releasedev.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MACOSX_DEPLOYMENT_TARGET = 12.6; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = stepanok.com.AppDatesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseDev; + }; + CE815A8E2D60D86800CB9114 /* ReleaseProd */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseProd; + }; + CE815A8F2D60D86800CB9114 /* ReleaseProd */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A4FF28047F07A8E553DD3C3F /* Pods-App-AppDates.releaseprod.xcconfig */; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AppDates; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseProd; + }; + CE815A902D60D86800CB9114 /* ReleaseProd */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 530623E65DE5652AD8E95ABC /* Pods-App-AppDates-AppDatesTests.releaseprod.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MACOSX_DEPLOYMENT_TARGET = 12.6; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = stepanok.com.AppDatesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseProd; + }; + CE815A912D60D86E00CB9114 /* ReleaseStage */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseStage; + }; + CE815A922D60D86E00CB9114 /* ReleaseStage */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B3A97A12BDD649309E86E47F /* Pods-App-AppDates.releasestage.xcconfig */; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AppDates; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseStage; + }; + CE815A932D60D86E00CB9114 /* ReleaseStage */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 14161693326A93866BB01A36 /* Pods-App-AppDates-AppDatesTests.releasestage.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MACOSX_DEPLOYMENT_TARGET = 12.6; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = stepanok.com.AppDatesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseStage; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + CE815A582D60D75200CB9114 /* Build configuration list for PBXProject "AppDates" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CE815A722D60D75200CB9114 /* Debug */, + CE815A882D60D85B00CB9114 /* DebugProd */, + CE815A852D60D85400CB9114 /* DebugStage */, + CE815A822D60D84C00CB9114 /* DebugDev */, + CE815A732D60D75200CB9114 /* Release */, + CE815A912D60D86E00CB9114 /* ReleaseStage */, + CE815A8E2D60D86800CB9114 /* ReleaseProd */, + CE815A8B2D60D86300CB9114 /* ReleaseDev */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CE815A6F2D60D75200CB9114 /* Build configuration list for PBXNativeTarget "AppDates" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CE815A702D60D75200CB9114 /* Debug */, + CE815A892D60D85B00CB9114 /* DebugProd */, + CE815A862D60D85400CB9114 /* DebugStage */, + CE815A832D60D84C00CB9114 /* DebugDev */, + CE815A712D60D75200CB9114 /* Release */, + CE815A922D60D86E00CB9114 /* ReleaseStage */, + CE815A8F2D60D86800CB9114 /* ReleaseProd */, + CE815A8C2D60D86300CB9114 /* ReleaseDev */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CE815A742D60D75200CB9114 /* Build configuration list for PBXNativeTarget "AppDatesTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CE815A752D60D75200CB9114 /* Debug */, + CE815A8A2D60D85B00CB9114 /* DebugProd */, + CE815A872D60D85400CB9114 /* DebugStage */, + CE815A842D60D84C00CB9114 /* DebugDev */, + CE815A762D60D75200CB9114 /* Release */, + CE815A932D60D86E00CB9114 /* ReleaseStage */, + CE815A902D60D86800CB9114 /* ReleaseProd */, + CE815A8D2D60D86300CB9114 /* ReleaseDev */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + CE815A7C2D60D7CC00CB9114 /* XCRemoteSwiftPackageReference "openedx-app-foundation-ios" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/openedx/openedx-app-foundation-ios/"; + requirement = { + kind = exactVersion; + version = 1.0.4; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + CE815A7D2D60D7CC00CB9114 /* OEXFoundation */ = { + isa = XCSwiftPackageProductDependency; + package = CE815A7C2D60D7CC00CB9114 /* XCRemoteSwiftPackageReference "openedx-app-foundation-ios" */; + productName = OEXFoundation; + }; + CE815A9B2D60DB8A00CB9114 /* OEXFoundation */ = { + isa = XCSwiftPackageProductDependency; + package = CE815A7C2D60D7CC00CB9114 /* XCRemoteSwiftPackageReference "openedx-app-foundation-ios" */; + productName = OEXFoundation; + }; +/* End XCSwiftPackageProductDependency section */ + +/* Begin XCVersionGroup section */ + CE8649892DA57A1C00821EC9 /* DatesCoreModel.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + CE8649A12DA57A1C00821EC9 /* DatesCoreModel.xcdatamodel */, + ); + currentVersion = CE8649A12DA57A1C00821EC9 /* DatesCoreModel.xcdatamodel */; + path = DatesCoreModel.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ + }; + rootObject = CE815A552D60D75200CB9114 /* Project object */; +} diff --git a/AppDates/AppDates/AppDates.h b/AppDates/AppDates/AppDates.h new file mode 100644 index 000000000..9c5d9863e --- /dev/null +++ b/AppDates/AppDates/AppDates.h @@ -0,0 +1,18 @@ +// +// AppDates.h +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +#import + +//! Project version number for AppDates. +FOUNDATION_EXPORT double AppDatesVersionNumber; + +//! Project version string for AppDates. +FOUNDATION_EXPORT const unsigned char AppDatesVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/AppDates/AppDates/Data/DatesPersistenceProtocol.swift b/AppDates/AppDates/Data/DatesPersistenceProtocol.swift new file mode 100644 index 000000000..67dc358cd --- /dev/null +++ b/AppDates/AppDates/Data/DatesPersistenceProtocol.swift @@ -0,0 +1,28 @@ +// +// DatesPersistenceProtocol.swift +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import Foundation +import Core + +//sourcery: AutoMockable +public protocol DatesPersistenceProtocol: Sendable { + func loadCourseDates(limit: Int?, offset: Int?) async throws -> [CourseDate] + func saveCourseDates(dates: [CourseDate], startIndex: Int) async + func clearAllCourseDates() async +} + +#if DEBUG +public struct DatesPersistenceMock: DatesPersistenceProtocol { + public func loadCourseDates(limit: Int?, offset: Int?) async throws -> [CourseDate] {[]} + public func saveCourseDates(dates: [CourseDate], startIndex: Int) async {} + public func clearAllCourseDates() async {} +} +#endif + +public final class AppDatesBundle { + private init() {} +} diff --git a/AppDates/AppDates/Data/DatesRepository.swift b/AppDates/AppDates/Data/DatesRepository.swift new file mode 100644 index 000000000..e64373aeb --- /dev/null +++ b/AppDates/AppDates/Data/DatesRepository.swift @@ -0,0 +1,184 @@ +// +// DatesRepository.swift +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import Foundation +import Core +import OEXFoundation + +public protocol DatesRepositoryProtocol: Sendable { + func getCourseDates(page: Int) async throws -> ([CourseDate], String?) + func getCourseDatesOffline(limit: Int?, offset: Int?) async throws -> [CourseDate] + func resetAllRelativeCourseDeadlines() async throws +} + +public actor DatesRepository: DatesRepositoryProtocol { + + private let api: API + private let storage: CoreStorage + private let config: ConfigProtocol + private let persistence: DatesPersistenceProtocol + private var totalItemsCount: Int = 0 + + public init(api: API, storage: CoreStorage, config: ConfigProtocol, persistence: DatesPersistenceProtocol) { + self.api = api + self.storage = storage + self.config = config + self.persistence = persistence + } + + public func getCourseDates(page: Int) async throws -> ([CourseDate], String?) { + let response = try await api.requestData( + DatesEndpoint.getCourseDates(username: storage.user?.username ?? "", page: page) + ) + .mapResponse(DataLayer.CourseDatesResponse.self) + + let dates = response.domain + + if page == 1 { + await persistence.clearAllCourseDates() + totalItemsCount = 0 + } + + let startIndex = totalItemsCount + let indexedDates = dates.enumerated().map { offset, date in + CourseDate( + date: date.date, + title: date.title, + courseName: date.courseName, + courseId: date.courseId, + blockId: date.blockId, + hasAccess: date.hasAccess, + order: startIndex + offset + ) + } + totalItemsCount += indexedDates.count + + await persistence.saveCourseDates(dates: indexedDates, startIndex: startIndex) + + return (indexedDates, response.next) + } + + public func getCourseDatesOffline(limit: Int? = nil, offset: Int? = nil) async throws -> [CourseDate] { + return try await persistence.loadCourseDates(limit: limit, offset: offset) + } + + public func resetAllRelativeCourseDeadlines() async throws { + try await api.request(DatesEndpoint.resetAllRelativeCourseDeadlines) + } +} + +// Mark - For testing and SwiftUI preview +#if DEBUG +public final class DatesRepositoryMock: DatesRepositoryProtocol { + + public init() {} + + public func getCourseDates(page: Int) async throws -> ([CourseDate], String?) { + let dates = [ + CourseDate( + date: Date().addingTimeInterval(-86400 * 2), + title: "Assignment from the Day Before Yesterday", + courseName: "Course 6", + courseId: "course-v1:1+1+daybeforeyesterday", + blockId: "block-v1:1+1+daybeforeyesterday+type@sequential+block@assignment", + hasAccess: true + ), + CourseDate( + date: Date().addingTimeInterval(-86400), + title: "Assignment from Yesterday", + courseName: "Course 7", + courseId: "course-v1:1+1+yesterday", + blockId: "block-v1:1+1+yesterday+type@sequential+block@assignment", + hasAccess: true + ), + CourseDate( + date: Date().addingTimeInterval(60 * 10), + title: "Today's Assignment 1", + courseName: "Course 1", + courseId: "course-v1:1+1+today1", + blockId: "block-v1:1+1+today1+type@sequential+block@assignment1", + hasAccess: true + ), + CourseDate( + date: Date().addingTimeInterval(60 * 20), + title: "Today's Assignment 2", + courseName: "Course 1", + courseId: "course-v1:1+1+today2", + blockId: "block-v1:1+1+today2+type@sequential+block@assignment2", + hasAccess: true + ), + CourseDate( + date: Date().addingTimeInterval(86400), // 1 day + title: "Tomorrow's Assignment", + courseName: "Course 2", + courseId: "course-v1:1+1+tomorrow1", + blockId: "block-v1:1+1+tomorrow1+type@sequential+block@assignment3", + hasAccess: true + ), + CourseDate( + date: Date().addingTimeInterval(86400 * 5), + title: "Assignment in 5 Days", + courseName: "Course 3", + courseId: "course-v1:1+1+5days1", + blockId: "block-v1:1+1+5days1+type@sequential+block@assignment4", + hasAccess: true + ), + CourseDate( + date: Date().addingTimeInterval(86400 * 5), + title: "Assignment in 5 Days (Part 2)", + courseName: "Course 3", + courseId: "course-v1:1+1+5days2", + blockId: "block-v1:1+1+5days2+type@sequential+block@assignment5", + hasAccess: true + ), + CourseDate( + date: Date().addingTimeInterval(86400 * 10), + title: "Assignment in 10 Days", + courseName: "Course 4", + courseId: "course-v1:1+1+10days1", + blockId: "block-v1:1+1+10days1+type@sequential+block@assignment6", + hasAccess: true + ), + CourseDate( + date: Date().addingTimeInterval(86400 * 20), + title: "Assignment in 20 Days 1", + courseName: "Course 5", + courseId: "course-v1:1+1+20days1", + blockId: "block-v1:1+1+20days1+type@sequential+block@assignment7", + hasAccess: true + ), + CourseDate( + date: Date().addingTimeInterval(86400 * 20), + title: "Assignment in 20 Days 2", + courseName: "Course 5", + courseId: "course-v1:1+1+20days2", + blockId: "block-v1:1+1+20days2+type@sequential+block@assignment8", + hasAccess: true + ) + ] + + return (dates, nil) + } + + public func getCourseDatesOffline(limit: Int? = nil, offset: Int? = nil) async throws -> [CourseDate] { + return [ + CourseDate( + date: Date().addingTimeInterval(-86400 * 3), + title: "Offline Assignment", + courseName: "Cached Course", + courseId: "course-v1:1+1+offline", + blockId: "block-v1:1+1+offline+type@sequential+block@bafd854414124f6db42fee42ca8acc19", + hasAccess: true + ) + ] + } + + public func resetAllRelativeCourseDeadlines() async throws {} + + public func clearAllCourseDates() async {} +} +#endif diff --git a/AppDates/AppDates/Data/Model/Data_Dates.swift b/AppDates/AppDates/Data/Model/Data_Dates.swift new file mode 100644 index 000000000..a01e9d660 --- /dev/null +++ b/AppDates/AppDates/Data/Model/Data_Dates.swift @@ -0,0 +1,81 @@ +// +// Data_Dates.swift +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import Foundation +import Core + +public extension DataLayer { + // MARK: - CourseDatesResponse + struct CourseDatesResponse: Codable { + public let count: Int + public let next: String? + public let previous: String? + public let results: [CourseDateItem] + + public init(count: Int, next: String?, previous: String?, results: [CourseDateItem]) { + self.count = count + self.next = next + self.previous = previous + self.results = results + } + } + + // MARK: - CourseDateItem + struct CourseDateItem: Codable { + public let courseId: String + public let assignmentBlockId: String? + public let dueDate: String + public let assignmentTitle: String + public let learnerHasAccess: Bool + public let courseName: String + public let location: String? + + enum CodingKeys: String, CodingKey { + case courseId = "course_id" + case assignmentBlockId = "first_component_block_id" + case dueDate = "due_date" + case assignmentTitle = "assignment_title" + case learnerHasAccess = "learner_has_access" + case courseName = "course_name" + case location + } + + public init( + courseId: String, + assignmentBlockId: String?, + dueDate: String, + assignmentTitle: String, + learnerHasAccess: Bool, + courseName: String, + location: String? = nil + ) { + self.courseId = courseId + self.assignmentBlockId = assignmentBlockId + self.dueDate = dueDate + self.assignmentTitle = assignmentTitle + self.learnerHasAccess = learnerHasAccess + self.courseName = courseName + self.location = location + } + } +} + +// Extension for domain conversion +public extension DataLayer.CourseDatesResponse { + var domain: [CourseDate] { + return results.map { result in + CourseDate( + date: Date(iso8601: result.dueDate), + title: result.assignmentTitle, + courseName: result.courseName, + courseId: result.courseId, + blockId: result.assignmentBlockId ?? result.location, + hasAccess: result.learnerHasAccess + ) + } + } +} diff --git a/AppDates/AppDates/Data/Network/DatesEndpoint.swift b/AppDates/AppDates/Data/Network/DatesEndpoint.swift new file mode 100644 index 000000000..af3ff9f32 --- /dev/null +++ b/AppDates/AppDates/Data/Network/DatesEndpoint.swift @@ -0,0 +1,51 @@ +// +// DatesEndpoint.swift +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import Foundation +import Core +import Alamofire +import OEXFoundation + +enum DatesEndpoint: EndPointType { + case getCourseDates(username: String, page: Int) + case resetAllRelativeCourseDeadlines + + var path: String { + switch self { + case let .getCourseDates(username, _): + return "/api/mobile/v3/course_dates/\(username)/" + case .resetAllRelativeCourseDeadlines: + return "/api/course_experience/v1/reset_all_relative_course_deadlines/" + } + } + + var httpMethod: HTTPMethod { + switch self { + case .getCourseDates: + return .get + case .resetAllRelativeCourseDeadlines: + return .post + } + } + + var headers: HTTPHeaders? { + nil + } + + var task: HTTPTask { + switch self { + case let .getCourseDates(_, page): + let params: Parameters = [ + "page": page + ] + return .requestParameters(parameters: params, encoding: URLEncoding.queryString) + + case .resetAllRelativeCourseDeadlines: + return .request + } + } +} diff --git a/AppDates/AppDates/Data/Persistence/DatesCoreModel.xcdatamodeld/DatesCoreModel.xcdatamodel/contents b/AppDates/AppDates/Data/Persistence/DatesCoreModel.xcdatamodeld/DatesCoreModel.xcdatamodel/contents new file mode 100644 index 000000000..5b3fe7056 --- /dev/null +++ b/AppDates/AppDates/Data/Persistence/DatesCoreModel.xcdatamodeld/DatesCoreModel.xcdatamodel/contents @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AppDates/AppDates/Domain/CourseDate.swift b/AppDates/AppDates/Domain/CourseDate.swift new file mode 100644 index 000000000..6eab5f1d4 --- /dev/null +++ b/AppDates/AppDates/Domain/CourseDate.swift @@ -0,0 +1,51 @@ +// +// CourseDate.swift +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +public struct CourseDate: Identifiable, Sendable { + public var id: String { + var components: [String] = [ + courseName, + title, + String(Int(date.timeIntervalSince1970)) + ] + if let courseId { + components.append(courseId) + } + if let blockId { + components.append(blockId) + } + if let order { + components.append("order:\(order)") + } + return components.joined(separator: "|") + } + public let date: Date + public let title: String + public let courseName: String + public let courseId: String? + public let blockId: String? + public let hasAccess: Bool + public let order: Int? + + public init( + date: Date, + title: String, + courseName: String, + courseId: String? = nil, + blockId: String? = nil, + hasAccess: Bool = true, + order: Int? = nil + ) { + self.date = date + self.title = title + self.courseName = courseName + self.courseId = courseId + self.blockId = blockId + self.hasAccess = hasAccess + self.order = order + } +} diff --git a/AppDates/AppDates/Domain/DateGroup.swift b/AppDates/AppDates/Domain/DateGroup.swift new file mode 100644 index 000000000..0e3c79e5d --- /dev/null +++ b/AppDates/AppDates/Domain/DateGroup.swift @@ -0,0 +1,20 @@ +// +// DateGroup.swift +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import Foundation + +public struct DateGroup: Identifiable { + public let id: String + public let type: DateGroupType + public let dates: [CourseDate] + + public init(type: DateGroupType, dates: [CourseDate]) { + self.id = type.text + self.type = type + self.dates = dates + } +} diff --git a/AppDates/AppDates/Domain/DateGroupType.swift b/AppDates/AppDates/Domain/DateGroupType.swift new file mode 100644 index 000000000..9c846c6f0 --- /dev/null +++ b/AppDates/AppDates/Domain/DateGroupType.swift @@ -0,0 +1,47 @@ +// +// DateGroupType.swift +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import SwiftUI +import Theme + +public enum DateGroupType: CaseIterable { + case pastDue + case today + case thisWeek + case nextWeek + case upcoming + + var text: String { + switch self { + case .pastDue: + AppDatesLocalization.Dates.pastDue + case .today: + AppDatesLocalization.Dates.today + case .thisWeek: + AppDatesLocalization.Dates.thisWeek + case .nextWeek: + AppDatesLocalization.Dates.nextWeek + case .upcoming: + AppDatesLocalization.Dates.upcoming + } + } + + var color: Color { + switch self { + case .pastDue: + Theme.Colors.pastDueTimelineColor + case .today: + Theme.Colors.todayTimelineColor + case .thisWeek: + Theme.Colors.thisWeekTimelineColor + case .nextWeek: + Theme.Colors.nextWeekTimelineColor + case .upcoming: + Theme.Colors.upcomingTimelineColor + } + } +} diff --git a/AppDates/AppDates/Domain/DatesInteractor.swift b/AppDates/AppDates/Domain/DatesInteractor.swift new file mode 100644 index 000000000..772db3af0 --- /dev/null +++ b/AppDates/AppDates/Domain/DatesInteractor.swift @@ -0,0 +1,44 @@ +// +// DatesInteractor.swift +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import Foundation +import Core + +//sourcery: AutoMockable +public protocol DatesInteractorProtocol: Sendable { + func getCourseDates(page: Int) async throws -> ([CourseDate], String?) + func getCourseDatesOffline(limit: Int?, offset: Int?) async throws -> [CourseDate] + func resetAllRelativeCourseDeadlines() async throws +} + +public actor DatesInteractor: DatesInteractorProtocol { + + private let repository: DatesRepositoryProtocol + + public init(repository: DatesRepositoryProtocol) { + self.repository = repository + } + + public func getCourseDates(page: Int) async throws -> ([CourseDate], String?) { + return try await repository.getCourseDates(page: page) + } + + public func getCourseDatesOffline(limit: Int?, offset: Int?) async throws -> [CourseDate] { + return try await repository.getCourseDatesOffline(limit: limit, offset: offset) + } + + public func resetAllRelativeCourseDeadlines() async throws { + try await repository.resetAllRelativeCourseDeadlines() + } +} + +// Mark - For testing and SwiftUI preview +#if DEBUG +public extension DatesInteractor { + static let mock = DatesInteractor(repository: DatesRepositoryMock()) +} +#endif diff --git a/AppDates/AppDates/Presentation/AppDatesAnalytics.swift b/AppDates/AppDates/Presentation/AppDatesAnalytics.swift new file mode 100644 index 000000000..fc58f0569 --- /dev/null +++ b/AppDates/AppDates/Presentation/AppDatesAnalytics.swift @@ -0,0 +1,25 @@ +// +// AppDatesAnalytics.swift +// AppDates +// +// Created by Ivan Stepanok on 17.03.2025. +// + +import Foundation + +//sourcery: AutoMockable +public protocol AppDatesAnalytics { + func mainDatesScreenViewed() + func datesCourseClicked(courseId: String, courseName: String) + func datesSettingsClicked() + func datesRefreshPulled() +} + +#if DEBUG +class AppDatesAnalyticsMock: AppDatesAnalytics { + public func mainDatesScreenViewed() {} + public func datesCourseClicked(courseId: String, courseName: String) {} + public func datesSettingsClicked() {} + public func datesRefreshPulled() {} +} +#endif diff --git a/AppDates/AppDates/Presentation/AppDatesRouter.swift b/AppDates/AppDates/Presentation/AppDatesRouter.swift new file mode 100644 index 000000000..4ce369f5b --- /dev/null +++ b/AppDates/AppDates/Presentation/AppDatesRouter.swift @@ -0,0 +1,47 @@ +// +// AppDatesRouter.swift +// AppDates +// +// Created by Ivan Stepanok on 17.03.2025. +// + +import Foundation +import Core + +@MainActor +public protocol AppDatesRouter: BaseRouter { + func showSettings() + func showUpdateRequiredView(showAccountLink: Bool) + func showCourseScreens( + courseID: String, + hasAccess: Bool?, + courseStart: Date?, + courseEnd: Date?, + enrollmentStart: Date?, + enrollmentEnd: Date?, + title: String, + courseRawImage: String?, + showDates: Bool, + lastVisitedBlockID: String? + ) +} + +// Mark - For testing and SwiftUI preview +#if DEBUG +public class AppDatesRouterMock: BaseRouterMock, AppDatesRouter { + public func showSettings() {} + public func showUpdateRequiredView(showAccountLink: Bool) {} + public func showCourseScreens( + courseID: String, + hasAccess: Bool?, + courseStart: Date?, + courseEnd: Date?, + enrollmentStart: Date?, + enrollmentEnd: Date?, + title: String, + courseRawImage: String?, + showDates: Bool, + lastVisitedBlockID: String? + ) {} +} +#endif diff --git a/AppDates/AppDates/Presentation/Dates/DateCell.swift b/AppDates/AppDates/Presentation/Dates/DateCell.swift new file mode 100644 index 000000000..d9229310c --- /dev/null +++ b/AppDates/AppDates/Presentation/Dates/DateCell.swift @@ -0,0 +1,96 @@ +// +// DateCell.swift +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import SwiftUI +import Theme +import Core + +public struct DateCell: View, Equatable { + + nonisolated public static func == (lhs: DateCell, rhs: DateCell) -> Bool { + return lhs.courseDate.blockId ?? "" == rhs.courseDate.blockId ?? "" + && lhs.courseDate.title == rhs.courseDate.title + && lhs.courseDate.date == rhs.courseDate.date + } + + private let courseDate: CourseDate + private let isFirst: Bool + private let isLast: Bool + private let groupType: DateGroupType + + public init( + courseDate: CourseDate, + groupType: DateGroupType, + isFirst: Bool = false, + isLast: Bool = false + ) { + self.courseDate = courseDate + self.groupType = groupType + self.isFirst = isFirst + self.isLast = isLast + } + + public var body: some View { + HStack(spacing: 0) { + Rectangle() + .fill(groupType.color) + .frame(width: 8) + .clipShape( + .rect( + topLeadingRadius: isFirst ? 4 : 0, + bottomLeadingRadius: isLast ? 4 : 0, + bottomTrailingRadius: isLast ? 4 : 0, + topTrailingRadius: isFirst ? 4 : 0 + ) + ) + + HStack(alignment: .center) { + VStack(alignment: .leading) { + if !courseDate.date.isToday { + Text(courseDate.date.dateToString(style: .shortWeekdayMonthDayYear, useRelativeDates: true)) + .font(Theme.Fonts.labelMedium) + .foregroundColor(Theme.Colors.textPrimary) + } + HStack { + CoreAssets.assignmentIcon.swiftUIImage + Text(courseDate.title) + .font(Theme.Fonts.titleMedium) + .lineLimit(1) + .multilineTextAlignment(.leading) + } + .foregroundColor(Theme.Colors.textPrimary) + + Text(courseDate.courseName) + .font(Theme.Fonts.labelMedium) + .foregroundColor(Theme.Colors.textPrimary) + .lineLimit(1) + .multilineTextAlignment(.leading) + } + + .padding(.leading, 12) + Spacer() + CoreAssets.chevronRight.swiftUIImage + .flipsForRightToLeftLayoutDirection(true) + .foregroundColor(Theme.Colors.textPrimary) + } + .padding(.vertical, 8) + } + } +} + +#if DEBUG +#Preview { + DateCell( + courseDate: CourseDate( + date: Date(), + title: "Assignment Title", + courseName: "Course Name" + ), + groupType: .pastDue + ) +} +#endif diff --git a/AppDates/AppDates/Presentation/Dates/DatesView.swift b/AppDates/AppDates/Presentation/Dates/DatesView.swift new file mode 100644 index 000000000..da8ecd384 --- /dev/null +++ b/AppDates/AppDates/Presentation/Dates/DatesView.swift @@ -0,0 +1,218 @@ +// +// DatesView.swift +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import SwiftUI +import Theme +import OEXFoundation +import Core + +public struct DatesView: View { + + @StateObject private var viewModel: DatesViewModel + + public init(viewModel: DatesViewModel) { + self._viewModel = StateObject(wrappedValue: { viewModel }()) + } + + public var body: some View { + GeometryReader { proxy in + VStack(spacing: 0) { + titleAndSettings(proxy: proxy) + .zIndex(1) + ZStack(alignment: .top) { + if viewModel.isShowProgress && !viewModel.noDates { + VStack(alignment: .center) { + ProgressBar(size: 40, lineWidth: 8) + }.frame(maxWidth: .infinity, + maxHeight: .infinity) + } else { + ScrollView { + if !viewModel.isShowProgress && viewModel.noDates { + DatesEmptyStateView() + } + if viewModel.showShiftDueDatesView { + ShiftDueDatesView( + isShowProgressForDueDates: $viewModel.isShowProgressForDueDates, + onShiftButtonTap: { + Task { + await viewModel.shiftDueDates() + } + } + ) + .padding(.horizontal, 24) + .padding(.bottom, 16) + } + LazyVStack(spacing: 16) { + ForEach(viewModel.coursesDates, id: \.id) { group in + LazyVStack(alignment: .leading, spacing: 0) { + Text(group.type.text) + .font(Theme.Fonts.titleMedium) + .foregroundColor(Theme.Colors.textPrimary) + .padding(.bottom, 8) + .padding(.top, 4) + + ForEach( + Array(group.dates.enumerated()), + id: \.element.id + ) { index, date in + Button(action: { + Task { + await viewModel.openVertical(date: date) + } + }, label: { + DateCell( + courseDate: date, + groupType: group.type, + isFirst: index == 0, + isLast: index == group.dates.count - 1 + ) + }) + .id(UUID()) + .onAppear { + Task { + await viewModel.loadNextPageIfNeeded(for: date) + } + } + } + } + .id(group.id) + } + + // Loading more indicator + if viewModel.isLoadingNextPage || viewModel.delayedLoadSecondPage { + ProgressBar(size: 40, lineWidth: 8) + .padding(.top, 20) + } + } + .padding(.horizontal, 24) + Spacer(minLength: 100) + } + .frameLimit(width: proxy.size.width) + .refreshableWithoutCancellation { + await viewModel.loadDates(isRefresh: true) + } + } + // MARK: - Offline mode SnackBar + OfflineSnackBarView( + connectivity: viewModel.connectivity, + reloadAction: { + Task { + await viewModel.loadDates(isRefresh: true) + } + } + ) + + // MARK: - Error Alert + if viewModel.showError { + VStack { + Spacer() + SnackBarView(message: viewModel.errorMessage) + } + .padding( + .bottom, + viewModel.connectivity.isInternetAvaliable + ? 0 : OfflineSnackBarView.height + ) + .transition(.move(edge: .bottom)) + .onAppear { + doAfter(Theme.Timeout.snackbarMessageLongTimeout) { + viewModel.errorMessage = nil + } + } + } + } + } + .onFirstAppear { + Task { + await viewModel.loadDates() + } + } + } + .accessibilityAction {} + .padding(.top, 8) + .navigationBarHidden(true) + .navigationBarBackButtonHidden(true) + .background( + Theme.Colors.background + .ignoresSafeArea() + ) + } + + private func titleAndSettings(proxy: GeometryProxy) -> some View { + ZStack(alignment: .top) { + Theme.Colors.background + .frame(height: 64) + ZStack(alignment: .topTrailing) { + VStack { + HStack(alignment: .center) { + Text(AppDatesLocalization.Dates.title) + .font(Theme.Fonts.displaySmall) + .foregroundColor(Theme.Colors.textPrimary) + .accessibilityIdentifier("dates_header_text") + Spacer() + } + } + .frameLimit(width: proxy.size.width) + + HStack { + Spacer() + Button(action: { + viewModel.router.showSettings() + }, label: { + CoreAssets.settings.swiftUIImage.renderingMode(.template) + .foregroundColor(Theme.Colors.accentColor) + }) + } + .padding(.top, 8) + .offset(x: UIDevice.current.userInterfaceIdiom == .pad ? 1 : 5, + y: UIDevice.current.userInterfaceIdiom == .pad ? 4 : -5) + } + .listRowBackground(Color.clear) + .padding(.horizontal, 20) + .accessibilityElement(children: .contain) + } + } +} + +struct DatesEmptyStateView: View { + @Environment(\.isHorizontal) private var isHorizontal + var body: some View { + VStack(alignment: .center, spacing: 0) { + CoreAssets.dates.swiftUIImage + .resizable() + .frame(width: 96, height: 96) + .foregroundStyle(Theme.Colors.textSecondaryLight) + .accessibilityIdentifier("empty_page_image") + Text(AppDatesLocalization.Empty.title) + .foregroundStyle(Theme.Colors.textPrimary) + .font(Theme.Fonts.titleMedium) + .accessibilityIdentifier("empty_page_subtitle_text") + Text(AppDatesLocalization.Empty.subtitle) + .multilineTextAlignment(.center) + .foregroundStyle(Theme.Colors.textPrimary) + .font(Theme.Fonts.labelMedium) + .frame(width: 245) + .accessibilityIdentifier("empty_page_subtitle_text") + } + .padding(.top, isHorizontal ? 20 : 200) + .frame(minWidth: 0, maxWidth: .infinity, alignment: .center) + } +} + +#if DEBUG +#Preview { + DatesView( + viewModel: DatesViewModel( + interactor: DatesInteractor.mock, + connectivity: Connectivity(), + courseManager: CourseStructureManagerMock(), + analytics: AppDatesAnalyticsMock(), + router: AppDatesRouterMock() + ) + ) +} +#endif diff --git a/AppDates/AppDates/Presentation/Dates/DatesViewModel.swift b/AppDates/AppDates/Presentation/Dates/DatesViewModel.swift new file mode 100644 index 000000000..3b1798ed1 --- /dev/null +++ b/AppDates/AppDates/Presentation/Dates/DatesViewModel.swift @@ -0,0 +1,303 @@ +// +// DatesViewModel.swift +// AppDates +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import Combine +import Core +import SwiftUI + +@MainActor +public final class DatesViewModel: ObservableObject { + + @Published public var coursesDates: [DateGroup] = [] + @Published private(set) var isShowProgress = false + @Published var showError: Bool = false + @Published private(set) var isLoadingNextPage = false + @Published private(set) var noDates = false + @Published var showShiftDueDatesView = false + @Published var isShowProgressForDueDates = false + @Published var delayedLoadSecondPage = false + + var errorMessage: String? { + didSet { + withAnimation { + showError = errorMessage != nil + } + } + } + + // Pagination properties + private var nextPage = 1 + private var hasNextPage = false + private(set) var fetchInProgress = false + private var allDates: [CourseDate] = [] + private var datesLoadedFromServer = false + private var shouldSkipOfflineLoad = false + + // Items per page constant + private let itemsPerPage = 20 + + let connectivity: ConnectivityProtocol + private let interactor: DatesInteractorProtocol + private let courseManager: CourseStructureManagerProtocol + private let analytics: AppDatesAnalytics + private(set) var router: AppDatesRouter + + public init( + interactor: DatesInteractorProtocol, + connectivity: ConnectivityProtocol, + courseManager: CourseStructureManagerProtocol, + analytics: AppDatesAnalytics, + router: AppDatesRouter + ) { + self.interactor = interactor + self.connectivity = connectivity + self.courseManager = courseManager + self.analytics = analytics + self.router = router + } + + @MainActor + public func loadDates(isRefresh: Bool = false) async { + if isRefresh { + analytics.datesRefreshPulled() + } + + isShowProgress = !isRefresh + + if isRefresh { + nextPage = 1 + hasNextPage = false + delayedLoadSecondPage = false + datesLoadedFromServer = false + } + + do { + if connectivity.isInternetAvaliable { + + if !shouldSkipOfflineLoad { + let offlineDates = try await interactor.getCourseDatesOffline(limit: itemsPerPage, offset: 0) + + allDates = offlineDates + self.processDates(allDates) + + if !offlineDates.isEmpty && nextPage == 1 { + isShowProgress = false + fetchInProgress = true + } + } + + let (dates, nextPageUrl) = try await interactor.getCourseDates(page: nextPage) + datesLoadedFromServer = true + + allDates = dates + hasNextPage = nextPageUrl != nil + + if hasNextPage { + nextPage += 1 + } else { + delayedLoadSecondPage = false + } + + if delayedLoadSecondPage, hasNextPage { + await loadNextPageIfNeeded(for: allDates[allDates.count - 3]) + } + + } else { + let dates = try await interactor.getCourseDatesOffline(limit: nil, offset: nil) + allDates = [] + coursesDates = [] + allDates = dates + hasNextPage = false + } + noDates = allDates.isEmpty + self.isShowProgress = false + self.fetchInProgress = false + self.processDates(allDates) + shouldSkipOfflineLoad = false + } catch let error { + isShowProgress = false + shouldSkipOfflineLoad = false + if error is NoCachedDataError { + errorMessage = CoreLocalization.Error.slowOrNoInternetConnection + } else if error.isUpdateRequeiredError { + self.router.showUpdateRequiredView(showAccountLink: true) + } else { + errorMessage = CoreLocalization.Error.unknownError + } + } + } + + public func loadNextPageIfNeeded(for date: CourseDate) async { + guard let index = allDates.firstIndex(where: { $0.id == date.id }) else { return } + + if !datesLoadedFromServer + && !hasNextPage + && nextPage == 1 + && index == allDates.count - 3 + && connectivity.isInternetAvaliable { + delayedLoadSecondPage = true + fetchInProgress = false + return + } + guard connectivity.isInternetAvaliable + && !fetchInProgress + && hasNextPage + && index == allDates.count - 3 else { return } + + fetchInProgress = true + isLoadingNextPage = true + + if delayedLoadSecondPage { + delayedLoadSecondPage = false + } + + do { + let (newDates, nextPageUrl) = try await interactor.getCourseDates(page: nextPage) + allDates.append(contentsOf: newDates) + + hasNextPage = nextPageUrl != nil + + if hasNextPage { + nextPage += 1 + } + + processDates(allDates) + fetchInProgress = false + isLoadingNextPage = false + } catch let error { + isShowProgress = false + isLoadingNextPage = false + if error is NoCachedDataError { + errorMessage = CoreLocalization.Error.slowOrNoInternetConnection + } else if error.isUpdateRequeiredError { + self.router.showUpdateRequiredView(showAccountLink: true) + } else { + errorMessage = CoreLocalization.Error.unknownError + } + } + } + + func openVertical(date: CourseDate) async { + guard let courseId = date.courseId else { return } + + analytics.datesCourseClicked(courseId: courseId, courseName: date.courseName) + + router + .showCourseScreens( + courseID: courseId, + hasAccess: date.hasAccess, + courseStart: Date(), + courseEnd: Date(), + enrollmentStart: nil, + enrollmentEnd: nil, + title: date.courseName, + courseRawImage: nil, + showDates: false, + lastVisitedBlockID: date.blockId + ) + } + + private func processDates(_ dates: [CourseDate]) { + let now = Date() + let calendar = Calendar.current + + // Group dates by type + var pastDue: [CourseDate] = [] + var today: [CourseDate] = [] + var thisWeek: [CourseDate] = [] + var nextWeek: [CourseDate] = [] + var upcoming: [CourseDate] = [] + + let todayDate = Date() + + for date in dates { + if date.date < todayDate { + pastDue.append(date) + } else if calendar.isDateInToday(date.date) { + today.append(date) + } else if calendar.isDate(date.date, equalTo: now, toGranularity: .weekOfYear) { + thisWeek.append(date) + } else if calendar.isDate( + date.date, + equalTo: now.addingTimeInterval(7 * 24 * 60 * 60), + toGranularity: .weekOfYear + ) { + nextWeek.append(date) + } else { + upcoming.append(date) + } + } + + // Create date groups + var groups: [DateGroup] = [] + + if !pastDue.isEmpty { + groups.append(DateGroup(type: .pastDue, dates: pastDue)) + showShiftDueDatesView = true + } else { + showShiftDueDatesView = false + } + + if !today.isEmpty { + groups.append(DateGroup(type: .today, dates: today)) + } + + if !thisWeek.isEmpty { + groups.append(DateGroup(type: .thisWeek, dates: thisWeek)) + } + + if !nextWeek.isEmpty { + groups.append(DateGroup(type: .nextWeek, dates: nextWeek)) + } + + if !upcoming.isEmpty { + groups.append(DateGroup(type: .upcoming, dates: upcoming)) + } + + self.coursesDates = groups + } + + @MainActor + public func shiftDueDates() async { + isShowProgressForDueDates = true + + do { + try await interactor.resetAllRelativeCourseDeadlines() + sendShiftDatesNotifications() + shouldSkipOfflineLoad = true + await loadDates(isRefresh: true) + } catch let error { + if error.isInternetError || error is NoCachedDataError { + errorMessage = CoreLocalization.Error.slowOrNoInternetConnection + } else { + errorMessage = CoreLocalization.Error.unknownError + } + isShowProgressForDueDates = false + return + } + withAnimation(.bouncy(duration: 0.15)) { + showShiftDueDatesView = false + } + isShowProgressForDueDates = false + } + + private func sendShiftDatesNotifications() { + struct Course: Hashable { + let id: String? + let name: String + } + + let pastDueCourses = coursesDates + .filter { $0.type == .pastDue } + .flatMap { $0.dates.compactMap { Course(id: $0.courseId, name: $0.courseName) } } + + for course in Set(pastDueCourses) { + guard let courseID = course.id else { continue } + NotificationCenter.default.post(name: .shiftCourseDates, object: (courseID, course.name)) + } + } +} diff --git a/AppDates/AppDates/Presentation/Dates/ShiftDueDatesView.swift b/AppDates/AppDates/Presentation/Dates/ShiftDueDatesView.swift new file mode 100644 index 000000000..d865a729d --- /dev/null +++ b/AppDates/AppDates/Presentation/Dates/ShiftDueDatesView.swift @@ -0,0 +1,59 @@ +// +// ShiftDueDatesView.swift +// AppDates +// +// Created by Ivan Stepanok on 25.03.2025. +// + +import SwiftUI +import Theme +import Core + +public struct ShiftDueDatesView: View { + @State private var isLoading = false + @Binding var isShowProgressForDueDates: Bool + var onShiftButtonTap: () -> Void + + public init( + isShowProgressForDueDates: Binding, + onShiftButtonTap: @escaping () -> Void + ) { + self._isShowProgressForDueDates = isShowProgressForDueDates + self.onShiftButtonTap = onShiftButtonTap + } + + public var body: some View { + VStack(alignment: .leading, spacing: 16) { + Text(AppDatesLocalization.ShiftDueDates.title) + .frame(maxWidth: .infinity, alignment: .leading) + .font(Theme.Fonts.titleMedium) + .foregroundColor(Theme.Colors.textPrimary) + + Text(AppDatesLocalization.ShiftDueDates.description) + .frame(maxWidth: .infinity, alignment: .leading) + .font(Theme.Fonts.labelLarge) + .foregroundColor(Theme.Colors.textPrimary) + + if isShowProgressForDueDates { + VStack(alignment: .center) { + ProgressBar(size: 40, lineWidth: 8) + }.frame(maxWidth: .infinity, + maxHeight: 42) + } else { + StyledButton( + AppDatesLocalization.ShiftDueDates.button, + action: { + onShiftButtonTap() + } + ) + } + } + .padding(16) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Theme.Colors.datesSectionStroke, lineWidth: 2) + ) + .background(Theme.Colors.datesSectionBackground) + .cornerRadius(8) + } +} diff --git a/AppDates/AppDates/SwiftGen/Strings.swift b/AppDates/AppDates/SwiftGen/Strings.swift new file mode 100644 index 000000000..b54698830 --- /dev/null +++ b/AppDates/AppDates/SwiftGen/Strings.swift @@ -0,0 +1,67 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return prefer_self_in_static_references + +// MARK: - Strings + +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +public enum AppDatesLocalization { + public enum Dates { + /// Next Week + public static let nextWeek = AppDatesLocalization.tr("Localizable", "DATES.NEXT_WEEK", fallback: "Next Week") + /// Past Due + public static let pastDue = AppDatesLocalization.tr("Localizable", "DATES.PAST_DUE", fallback: "Past Due") + /// This Week + public static let thisWeek = AppDatesLocalization.tr("Localizable", "DATES.THIS_WEEK", fallback: "This Week") + /// Localizable.strings + /// AppDates + /// + /// Created by Ivan Stepanok on 15.02.2025. + public static let title = AppDatesLocalization.tr("Localizable", "DATES.TITLE", fallback: "Dates") + /// Today + public static let today = AppDatesLocalization.tr("Localizable", "DATES.TODAY", fallback: "Today") + /// Upcoming + public static let upcoming = AppDatesLocalization.tr("Localizable", "DATES.UPCOMING", fallback: "Upcoming") + } + public enum Empty { + /// You currently have no active courses with scheduled events. Enroll in a course to view important dates and deadlines. + public static let subtitle = AppDatesLocalization.tr("Localizable", "EMPTY.SUBTITLE", fallback: "You currently have no active courses with scheduled events. Enroll in a course to view important dates and deadlines.") + /// No Dates + public static let title = AppDatesLocalization.tr("Localizable", "EMPTY.TITLE", fallback: "No Dates") + } + public enum ShiftDueDates { + /// Shift Due Dates + public static let button = AppDatesLocalization.tr("Localizable", "SHIFT_DUE_DATES.BUTTON", fallback: "Shift Due Dates") + /// Don't worry - shift our suggested schedule to complete the due assignments without losing any progress. + public static let description = AppDatesLocalization.tr("Localizable", "SHIFT_DUE_DATES.DESCRIPTION", fallback: "Don't worry - shift our suggested schedule to complete the due assignments without losing any progress.") + /// Missed Some Deadlines? + public static let title = AppDatesLocalization.tr("Localizable", "SHIFT_DUE_DATES.TITLE", fallback: "Missed Some Deadlines?") + } +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +extension AppDatesLocalization { + private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String { + let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table) + return String(format: format, locale: Locale.current, arguments: args) + } +} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type diff --git a/AppDates/AppDates/en.lproj/Localizable.strings b/AppDates/AppDates/en.lproj/Localizable.strings new file mode 100644 index 000000000..6fa9a95b7 --- /dev/null +++ b/AppDates/AppDates/en.lproj/Localizable.strings @@ -0,0 +1,21 @@ +/* + Localizable.strings + AppDates + + Created by Ivan Stepanok on 15.02.2025. + +*/ + +"DATES.TITLE" = "Dates"; +"DATES.PAST_DUE" = "Past Due"; +"DATES.TODAY" = "Today"; +"DATES.THIS_WEEK" = "This Week"; +"DATES.NEXT_WEEK" = "Next Week"; +"DATES.UPCOMING" = "Upcoming"; + +"EMPTY.TITLE" = "No Dates"; +"EMPTY.SUBTITLE" = "You currently have no active courses with scheduled events. Enroll in a course to view important dates and deadlines."; + +"SHIFT_DUE_DATES.TITLE" = "Missed Some Deadlines?"; +"SHIFT_DUE_DATES.DESCRIPTION" = "Don't worry - shift our suggested schedule to complete the due assignments without losing any progress."; +"SHIFT_DUE_DATES.BUTTON" = "Shift Due Dates"; diff --git a/AppDates/AppDatesTests/AppDatesMock.generated.swift b/AppDates/AppDatesTests/AppDatesMock.generated.swift new file mode 100644 index 000000000..7eb618240 --- /dev/null +++ b/AppDates/AppDatesTests/AppDatesMock.generated.swift @@ -0,0 +1,5295 @@ +// Generated using Sourcery 2.1.2 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT + + +// Generated with SwiftyMocky 4.2.0 +// Required Sourcery: 1.8.0 + + +import SwiftyMocky +import XCTest +import Core +import AppDates +import Foundation +import SwiftUI +import Combine +import OEXFoundation + + +// MARK: - AppDatesAnalytics + +open class AppDatesAnalyticsMock: AppDatesAnalytics, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + + + + + open func mainDatesScreenViewed() { + addInvocation(.m_mainDatesScreenViewed) + let perform = methodPerformValue(.m_mainDatesScreenViewed) as? () -> Void + perform?() + } + + open func datesCourseClicked(courseId: String, courseName: String) { + addInvocation(.m_datesCourseClicked__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) + let perform = methodPerformValue(.m_datesCourseClicked__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) as? (String, String) -> Void + perform?(`courseId`, `courseName`) + } + + open func datesSettingsClicked() { + addInvocation(.m_datesSettingsClicked) + let perform = methodPerformValue(.m_datesSettingsClicked) as? () -> Void + perform?() + } + + open func datesRefreshPulled() { + addInvocation(.m_datesRefreshPulled) + let perform = methodPerformValue(.m_datesRefreshPulled) as? () -> Void + perform?() + } + + + fileprivate enum MethodType { + case m_mainDatesScreenViewed + case m_datesCourseClicked__courseId_courseIdcourseName_courseName(Parameter, Parameter) + case m_datesSettingsClicked + case m_datesRefreshPulled + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_mainDatesScreenViewed, .m_mainDatesScreenViewed): return .match + + case (.m_datesCourseClicked__courseId_courseIdcourseName_courseName(let lhsCourseid, let lhsCoursename), .m_datesCourseClicked__courseId_courseIdcourseName_courseName(let rhsCourseid, let rhsCoursename)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseId")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCoursename, rhs: rhsCoursename, with: matcher), lhsCoursename, rhsCoursename, "courseName")) + return Matcher.ComparisonResult(results) + + case (.m_datesSettingsClicked, .m_datesSettingsClicked): return .match + + case (.m_datesRefreshPulled, .m_datesRefreshPulled): return .match + default: return .none + } + } + + func intValue() -> Int { + switch self { + case .m_mainDatesScreenViewed: return 0 + case let .m_datesCourseClicked__courseId_courseIdcourseName_courseName(p0, p1): return p0.intValue + p1.intValue + case .m_datesSettingsClicked: return 0 + case .m_datesRefreshPulled: return 0 + } + } + func assertionName() -> String { + switch self { + case .m_mainDatesScreenViewed: return ".mainDatesScreenViewed()" + case .m_datesCourseClicked__courseId_courseIdcourseName_courseName: return ".datesCourseClicked(courseId:courseName:)" + case .m_datesSettingsClicked: return ".datesSettingsClicked()" + case .m_datesRefreshPulled: return ".datesRefreshPulled()" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + + } + + public struct Verify { + fileprivate var method: MethodType + + public static func mainDatesScreenViewed() -> Verify { return Verify(method: .m_mainDatesScreenViewed)} + public static func datesCourseClicked(courseId: Parameter, courseName: Parameter) -> Verify { return Verify(method: .m_datesCourseClicked__courseId_courseIdcourseName_courseName(`courseId`, `courseName`))} + public static func datesSettingsClicked() -> Verify { return Verify(method: .m_datesSettingsClicked)} + public static func datesRefreshPulled() -> Verify { return Verify(method: .m_datesRefreshPulled)} + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func mainDatesScreenViewed(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_mainDatesScreenViewed, performs: perform) + } + public static func datesCourseClicked(courseId: Parameter, courseName: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_datesCourseClicked__courseId_courseIdcourseName_courseName(`courseId`, `courseName`), performs: perform) + } + public static func datesSettingsClicked(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_datesSettingsClicked, performs: perform) + } + public static func datesRefreshPulled(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_datesRefreshPulled, performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - AuthInteractorProtocol + +open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + + + + + @discardableResult + open func login(username: String, password: String) throws -> User { + addInvocation(.m_login__username_usernamepassword_password(Parameter.value(`username`), Parameter.value(`password`))) + let perform = methodPerformValue(.m_login__username_usernamepassword_password(Parameter.value(`username`), Parameter.value(`password`))) as? (String, String) -> Void + perform?(`username`, `password`) + var __value: User + do { + __value = try methodReturnValue(.m_login__username_usernamepassword_password(Parameter.value(`username`), Parameter.value(`password`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(username: String, password: String). Use given") + Failure("Stub return value not specified for login(username: String, password: String). Use given") + } catch { + throw error + } + return __value + } + + @discardableResult + open func login(externalToken: String, backend: String) throws -> User { + addInvocation(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) + let perform = methodPerformValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) as? (String, String) -> Void + perform?(`externalToken`, `backend`) + var __value: User + do { + __value = try methodReturnValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + Failure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + } catch { + throw error + } + return __value + } + + open func login(ssoToken: String) throws -> User { + addInvocation(.m_login__ssoToken_ssoToken(Parameter.value(`ssoToken`))) + let perform = methodPerformValue(.m_login__ssoToken_ssoToken(Parameter.value(`ssoToken`))) as? (String) -> Void + perform?(`ssoToken`) + var __value: User + do { + __value = try methodReturnValue(.m_login__ssoToken_ssoToken(Parameter.value(`ssoToken`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(ssoToken: String). Use given") + Failure("Stub return value not specified for login(ssoToken: String). Use given") + } catch { + throw error + } + return __value + } + + open func resetPassword(email: String) throws -> ResetPassword { + addInvocation(.m_resetPassword__email_email(Parameter.value(`email`))) + let perform = methodPerformValue(.m_resetPassword__email_email(Parameter.value(`email`))) as? (String) -> Void + perform?(`email`) + var __value: ResetPassword + do { + __value = try methodReturnValue(.m_resetPassword__email_email(Parameter.value(`email`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for resetPassword(email: String). Use given") + Failure("Stub return value not specified for resetPassword(email: String). Use given") + } catch { + throw error + } + return __value + } + + open func getCookies(force: Bool) throws { + addInvocation(.m_getCookies__force_force(Parameter.value(`force`))) + let perform = methodPerformValue(.m_getCookies__force_force(Parameter.value(`force`))) as? (Bool) -> Void + perform?(`force`) + do { + _ = try methodReturnValue(.m_getCookies__force_force(Parameter.value(`force`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + + open func getRegistrationFields() throws -> [PickerFields] { + addInvocation(.m_getRegistrationFields) + let perform = methodPerformValue(.m_getRegistrationFields) as? () -> Void + perform?() + var __value: [PickerFields] + do { + __value = try methodReturnValue(.m_getRegistrationFields).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for getRegistrationFields(). Use given") + Failure("Stub return value not specified for getRegistrationFields(). Use given") + } catch { + throw error + } + return __value + } + + open func registerUser(fields: [String: String], isSocial: Bool) throws -> User { + addInvocation(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) + let perform = methodPerformValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) as? ([String: String], Bool) -> Void + perform?(`fields`, `isSocial`) + var __value: User + do { + __value = try methodReturnValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") + Failure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") + } catch { + throw error + } + return __value + } + + open func validateRegistrationFields(fields: [String: String]) throws -> [String: String] { + addInvocation(.m_validateRegistrationFields__fields_fields(Parameter<[String: String]>.value(`fields`))) + let perform = methodPerformValue(.m_validateRegistrationFields__fields_fields(Parameter<[String: String]>.value(`fields`))) as? ([String: String]) -> Void + perform?(`fields`) + var __value: [String: String] + do { + __value = try methodReturnValue(.m_validateRegistrationFields__fields_fields(Parameter<[String: String]>.value(`fields`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for validateRegistrationFields(fields: [String: String]). Use given") + Failure("Stub return value not specified for validateRegistrationFields(fields: [String: String]). Use given") + } catch { + throw error + } + return __value + } + + + fileprivate enum MethodType { + case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) + case m_login__ssoToken_ssoToken(Parameter) + case m_resetPassword__email_email(Parameter) + case m_getCookies__force_force(Parameter) + case m_getRegistrationFields + case m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>, Parameter) + case m_validateRegistrationFields__fields_fields(Parameter<[String: String]>) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_login__username_usernamepassword_password(let lhsUsername, let lhsPassword), .m_login__username_usernamepassword_password(let rhsUsername, let rhsPassword)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsUsername, rhs: rhsUsername, with: matcher), lhsUsername, rhsUsername, "username")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPassword, rhs: rhsPassword, with: matcher), lhsPassword, rhsPassword, "password")) + return Matcher.ComparisonResult(results) + + case (.m_login__externalToken_externalTokenbackend_backend(let lhsExternaltoken, let lhsBackend), .m_login__externalToken_externalTokenbackend_backend(let rhsExternaltoken, let rhsBackend)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsExternaltoken, rhs: rhsExternaltoken, with: matcher), lhsExternaltoken, rhsExternaltoken, "externalToken")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBackend, rhs: rhsBackend, with: matcher), lhsBackend, rhsBackend, "backend")) + return Matcher.ComparisonResult(results) + + case (.m_login__ssoToken_ssoToken(let lhsSsotoken), .m_login__ssoToken_ssoToken(let rhsSsotoken)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsSsotoken, rhs: rhsSsotoken, with: matcher), lhsSsotoken, rhsSsotoken, "ssoToken")) + return Matcher.ComparisonResult(results) + + case (.m_resetPassword__email_email(let lhsEmail), .m_resetPassword__email_email(let rhsEmail)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEmail, rhs: rhsEmail, with: matcher), lhsEmail, rhsEmail, "email")) + return Matcher.ComparisonResult(results) + + case (.m_getCookies__force_force(let lhsForce), .m_getCookies__force_force(let rhsForce)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) + return Matcher.ComparisonResult(results) + + case (.m_getRegistrationFields, .m_getRegistrationFields): return .match + + case (.m_registerUser__fields_fieldsisSocial_isSocial(let lhsFields, let lhsIssocial), .m_registerUser__fields_fieldsisSocial_isSocial(let rhsFields, let rhsIssocial)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFields, rhs: rhsFields, with: matcher), lhsFields, rhsFields, "fields")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsIssocial, rhs: rhsIssocial, with: matcher), lhsIssocial, rhsIssocial, "isSocial")) + return Matcher.ComparisonResult(results) + + case (.m_validateRegistrationFields__fields_fields(let lhsFields), .m_validateRegistrationFields__fields_fields(let rhsFields)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFields, rhs: rhsFields, with: matcher), lhsFields, rhsFields, "fields")) + return Matcher.ComparisonResult(results) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue + case let .m_login__ssoToken_ssoToken(p0): return p0.intValue + case let .m_resetPassword__email_email(p0): return p0.intValue + case let .m_getCookies__force_force(p0): return p0.intValue + case .m_getRegistrationFields: return 0 + case let .m_registerUser__fields_fieldsisSocial_isSocial(p0, p1): return p0.intValue + p1.intValue + case let .m_validateRegistrationFields__fields_fields(p0): return p0.intValue + } + } + func assertionName() -> String { + switch self { + case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" + case .m_login__ssoToken_ssoToken: return ".login(ssoToken:)" + case .m_resetPassword__email_email: return ".resetPassword(email:)" + case .m_getCookies__force_force: return ".getCookies(force:)" + case .m_getRegistrationFields: return ".getRegistrationFields()" + case .m_registerUser__fields_fieldsisSocial_isSocial: return ".registerUser(fields:isSocial:)" + case .m_validateRegistrationFields__fields_fields: return ".validateRegistrationFields(fields:)" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + + @discardableResult + public static func login(username: Parameter, password: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_login__username_usernamepassword_password(`username`, `password`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func login(ssoToken: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_login__ssoToken_ssoToken(`ssoToken`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func resetPassword(email: Parameter, willReturn: ResetPassword...) -> MethodStub { + return Given(method: .m_resetPassword__email_email(`email`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getRegistrationFields(willReturn: [PickerFields]...) -> MethodStub { + return Given(method: .m_getRegistrationFields, products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func validateRegistrationFields(fields: Parameter<[String: String]>, willReturn: [String: String]...) -> MethodStub { + return Given(method: .m_validateRegistrationFields__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + @discardableResult + public static func login(username: Parameter, password: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_login__username_usernamepassword_password(`username`, `password`), products: willThrow.map({ StubProduct.throw($0) })) + } + @discardableResult + public static func login(username: Parameter, password: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_login__username_usernamepassword_password(`username`, `password`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (User).self) + willProduce(stubber) + return given + } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) + } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (User).self) + willProduce(stubber) + return given + } + public static func login(ssoToken: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_login__ssoToken_ssoToken(`ssoToken`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func login(ssoToken: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_login__ssoToken_ssoToken(`ssoToken`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (User).self) + willProduce(stubber) + return given + } + public static func resetPassword(email: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_resetPassword__email_email(`email`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func resetPassword(email: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_resetPassword__email_email(`email`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (ResetPassword).self) + willProduce(stubber) + return given + } + public static func getCookies(force: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getCookies__force_force(`force`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func getCookies(force: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_getCookies__force_force(`force`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } + public static func getRegistrationFields(willThrow: Error...) -> MethodStub { + return Given(method: .m_getRegistrationFields, products: willThrow.map({ StubProduct.throw($0) })) + } + public static func getRegistrationFields(willProduce: (StubberThrows<[PickerFields]>) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_getRegistrationFields, products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: ([PickerFields]).self) + willProduce(stubber) + return given + } + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (User).self) + willProduce(stubber) + return given + } + public static func validateRegistrationFields(fields: Parameter<[String: String]>, willThrow: Error...) -> MethodStub { + return Given(method: .m_validateRegistrationFields__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func validateRegistrationFields(fields: Parameter<[String: String]>, willProduce: (StubberThrows<[String: String]>) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_validateRegistrationFields__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: ([String: String]).self) + willProduce(stubber) + return given + } + } + + public struct Verify { + fileprivate var method: MethodType + + @discardableResult + public static func login(username: Parameter, password: Parameter) -> Verify { return Verify(method: .m_login__username_usernamepassword_password(`username`, `password`))} + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter) -> Verify { return Verify(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`))} + public static func login(ssoToken: Parameter) -> Verify { return Verify(method: .m_login__ssoToken_ssoToken(`ssoToken`))} + public static func resetPassword(email: Parameter) -> Verify { return Verify(method: .m_resetPassword__email_email(`email`))} + public static func getCookies(force: Parameter) -> Verify { return Verify(method: .m_getCookies__force_force(`force`))} + public static func getRegistrationFields() -> Verify { return Verify(method: .m_getRegistrationFields)} + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter) -> Verify { return Verify(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`))} + public static func validateRegistrationFields(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_validateRegistrationFields__fields_fields(`fields`))} + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + @discardableResult + public static func login(username: Parameter, password: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_login__username_usernamepassword_password(`username`, `password`), performs: perform) + } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), performs: perform) + } + public static func login(ssoToken: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_login__ssoToken_ssoToken(`ssoToken`), performs: perform) + } + public static func resetPassword(email: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_resetPassword__email_email(`email`), performs: perform) + } + public static func getCookies(force: Parameter, perform: @escaping (Bool) -> Void) -> Perform { + return Perform(method: .m_getCookies__force_force(`force`), performs: perform) + } + public static func getRegistrationFields(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_getRegistrationFields, performs: perform) + } + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, perform: @escaping ([String: String], Bool) -> Void) -> Perform { + return Perform(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), performs: perform) + } + public static func validateRegistrationFields(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { + return Perform(method: .m_validateRegistrationFields__fields_fields(`fields`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - BaseRouter +@MainActor +open class BaseRouterMock: BaseRouter, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + + + + + open func backToRoot(animated: Bool) { + addInvocation(.m_backToRoot__animated_animated(Parameter.value(`animated`))) + let perform = methodPerformValue(.m_backToRoot__animated_animated(Parameter.value(`animated`))) as? (Bool) -> Void + perform?(`animated`) + } + + open func back(animated: Bool) { + addInvocation(.m_back__animated_animated(Parameter.value(`animated`))) + let perform = methodPerformValue(.m_back__animated_animated(Parameter.value(`animated`))) as? (Bool) -> Void + perform?(`animated`) + } + + open func backWithFade() { + addInvocation(.m_backWithFade) + let perform = methodPerformValue(.m_backWithFade) as? () -> Void + perform?() + } + + open func dismiss(animated: Bool) { + addInvocation(.m_dismiss__animated_animated(Parameter.value(`animated`))) + let perform = methodPerformValue(.m_dismiss__animated_animated(Parameter.value(`animated`))) as? (Bool) -> Void + perform?(`animated`) + } + + open func removeLastView(controllers: Int) { + addInvocation(.m_removeLastView__controllers_controllers(Parameter.value(`controllers`))) + let perform = methodPerformValue(.m_removeLastView__controllers_controllers(Parameter.value(`controllers`))) as? (Int) -> Void + perform?(`controllers`) + } + + open func showMainOrWhatsNewScreen(sourceScreen: LogistrationSourceScreen, postLoginData: PostLoginData?) { + addInvocation(.m_showMainOrWhatsNewScreen__sourceScreen_sourceScreenpostLoginData_postLoginData(Parameter.value(`sourceScreen`), Parameter.value(`postLoginData`))) + let perform = methodPerformValue(.m_showMainOrWhatsNewScreen__sourceScreen_sourceScreenpostLoginData_postLoginData(Parameter.value(`sourceScreen`), Parameter.value(`postLoginData`))) as? (LogistrationSourceScreen, PostLoginData?) -> Void + perform?(`sourceScreen`, `postLoginData`) + } + + open func showStartupScreen() { + addInvocation(.m_showStartupScreen) + let perform = methodPerformValue(.m_showStartupScreen) as? () -> Void + perform?() + } + + open func showLoginScreen(sourceScreen: LogistrationSourceScreen) { + addInvocation(.m_showLoginScreen__sourceScreen_sourceScreen(Parameter.value(`sourceScreen`))) + let perform = methodPerformValue(.m_showLoginScreen__sourceScreen_sourceScreen(Parameter.value(`sourceScreen`))) as? (LogistrationSourceScreen) -> Void + perform?(`sourceScreen`) + } + + open func showRegisterScreen(sourceScreen: LogistrationSourceScreen) { + addInvocation(.m_showRegisterScreen__sourceScreen_sourceScreen(Parameter.value(`sourceScreen`))) + let perform = methodPerformValue(.m_showRegisterScreen__sourceScreen_sourceScreen(Parameter.value(`sourceScreen`))) as? (LogistrationSourceScreen) -> Void + perform?(`sourceScreen`) + } + + open func showForgotPasswordScreen() { + addInvocation(.m_showForgotPasswordScreen) + let perform = methodPerformValue(.m_showForgotPasswordScreen) as? () -> Void + perform?() + } + + open func showDiscoveryScreen(searchQuery: String?, sourceScreen: LogistrationSourceScreen) { + addInvocation(.m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter.value(`searchQuery`), Parameter.value(`sourceScreen`))) + let perform = methodPerformValue(.m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter.value(`searchQuery`), Parameter.value(`sourceScreen`))) as? (String?, LogistrationSourceScreen) -> Void + perform?(`searchQuery`, `sourceScreen`) + } + + open func showWebBrowser(title: String, url: URL) { + addInvocation(.m_showWebBrowser__title_titleurl_url(Parameter.value(`title`), Parameter.value(`url`))) + let perform = methodPerformValue(.m_showWebBrowser__title_titleurl_url(Parameter.value(`title`), Parameter.value(`url`))) as? (String, URL) -> Void + perform?(`title`, `url`) + } + + open func showSSOWebBrowser(title: String) { + addInvocation(.m_showSSOWebBrowser__title_title(Parameter.value(`title`))) + let perform = methodPerformValue(.m_showSSOWebBrowser__title_title(Parameter.value(`title`))) as? (String) -> Void + perform?(`title`) + } + + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, firstButtonTapped: @escaping () -> Void, type: AlertViewType) { + addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`firstButtonTapped`), Parameter.value(`type`))) + let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`firstButtonTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void + perform?(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `firstButtonTapped`, `type`) + } + + open func presentAlert(alertTitle: String, alertMessage: String, nextSectionName: String?, action: String, image: SwiftUI.Image, onCloseTapped: @escaping () -> Void, firstButtonTapped: @escaping () -> Void, nextSectionTapped: @escaping () -> Void) { + addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappednextSectionTapped_nextSectionTapped(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`nextSectionName`), Parameter.value(`action`), Parameter.value(`image`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`firstButtonTapped`), Parameter<() -> Void>.value(`nextSectionTapped`))) + let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappednextSectionTapped_nextSectionTapped(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`nextSectionName`), Parameter.value(`action`), Parameter.value(`image`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`firstButtonTapped`), Parameter<() -> Void>.value(`nextSectionTapped`))) as? (String, String, String?, String, SwiftUI.Image, @escaping () -> Void, @escaping () -> Void, @escaping () -> Void) -> Void + perform?(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `firstButtonTapped`, `nextSectionTapped`) + } + + open func presentView(transitionStyle: UIModalTransitionStyle, view: any View, completion: (() -> Void)?) { + addInvocation(.m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter.value(`transitionStyle`), Parameter.value(`view`), Parameter<(() -> Void)?>.value(`completion`))) + let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter.value(`transitionStyle`), Parameter.value(`view`), Parameter<(() -> Void)?>.value(`completion`))) as? (UIModalTransitionStyle, any View, (() -> Void)?) -> Void + perform?(`transitionStyle`, `view`, `completion`) + } + + open func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) { + addInvocation(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) + let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, Bool, () -> any View) -> Void + perform?(`transitionStyle`, `animated`, `content`) + } + + + fileprivate enum MethodType { + case m_backToRoot__animated_animated(Parameter) + case m_back__animated_animated(Parameter) + case m_backWithFade + case m_dismiss__animated_animated(Parameter) + case m_removeLastView__controllers_controllers(Parameter) + case m_showMainOrWhatsNewScreen__sourceScreen_sourceScreenpostLoginData_postLoginData(Parameter, Parameter) + case m_showStartupScreen + case m_showLoginScreen__sourceScreen_sourceScreen(Parameter) + case m_showRegisterScreen__sourceScreen_sourceScreen(Parameter) + case m_showForgotPasswordScreen + case m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter, Parameter) + case m_showWebBrowser__title_titleurl_url(Parameter, Parameter) + case m_showSSOWebBrowser__title_title(Parameter) + case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) + case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) + case m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter, Parameter, Parameter<(() -> Void)?>) + case m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter, Parameter, Parameter<() -> any View>) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_backToRoot__animated_animated(let lhsAnimated), .m_backToRoot__animated_animated(let rhsAnimated)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) + return Matcher.ComparisonResult(results) + + case (.m_back__animated_animated(let lhsAnimated), .m_back__animated_animated(let rhsAnimated)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) + return Matcher.ComparisonResult(results) + + case (.m_backWithFade, .m_backWithFade): return .match + + case (.m_dismiss__animated_animated(let lhsAnimated), .m_dismiss__animated_animated(let rhsAnimated)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) + return Matcher.ComparisonResult(results) + + case (.m_removeLastView__controllers_controllers(let lhsControllers), .m_removeLastView__controllers_controllers(let rhsControllers)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsControllers, rhs: rhsControllers, with: matcher), lhsControllers, rhsControllers, "controllers")) + return Matcher.ComparisonResult(results) + + case (.m_showMainOrWhatsNewScreen__sourceScreen_sourceScreenpostLoginData_postLoginData(let lhsSourcescreen, let lhsPostlogindata), .m_showMainOrWhatsNewScreen__sourceScreen_sourceScreenpostLoginData_postLoginData(let rhsSourcescreen, let rhsPostlogindata)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsSourcescreen, rhs: rhsSourcescreen, with: matcher), lhsSourcescreen, rhsSourcescreen, "sourceScreen")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPostlogindata, rhs: rhsPostlogindata, with: matcher), lhsPostlogindata, rhsPostlogindata, "postLoginData")) + return Matcher.ComparisonResult(results) + + case (.m_showStartupScreen, .m_showStartupScreen): return .match + + case (.m_showLoginScreen__sourceScreen_sourceScreen(let lhsSourcescreen), .m_showLoginScreen__sourceScreen_sourceScreen(let rhsSourcescreen)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsSourcescreen, rhs: rhsSourcescreen, with: matcher), lhsSourcescreen, rhsSourcescreen, "sourceScreen")) + return Matcher.ComparisonResult(results) + + case (.m_showRegisterScreen__sourceScreen_sourceScreen(let lhsSourcescreen), .m_showRegisterScreen__sourceScreen_sourceScreen(let rhsSourcescreen)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsSourcescreen, rhs: rhsSourcescreen, with: matcher), lhsSourcescreen, rhsSourcescreen, "sourceScreen")) + return Matcher.ComparisonResult(results) + + case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + + case (.m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(let lhsSearchquery, let lhsSourcescreen), .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(let rhsSearchquery, let rhsSourcescreen)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsSearchquery, rhs: rhsSearchquery, with: matcher), lhsSearchquery, rhsSearchquery, "searchQuery")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsSourcescreen, rhs: rhsSourcescreen, with: matcher), lhsSourcescreen, rhsSourcescreen, "sourceScreen")) + return Matcher.ComparisonResult(results) + + case (.m_showWebBrowser__title_titleurl_url(let lhsTitle, let lhsUrl), .m_showWebBrowser__title_titleurl_url(let rhsTitle, let rhsUrl)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTitle, rhs: rhsTitle, with: matcher), lhsTitle, rhsTitle, "title")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsUrl, rhs: rhsUrl, with: matcher), lhsUrl, rhsUrl, "url")) + return Matcher.ComparisonResult(results) + + case (.m_showSSOWebBrowser__title_title(let lhsTitle), .m_showSSOWebBrowser__title_title(let rhsTitle)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTitle, rhs: rhsTitle, with: matcher), lhsTitle, rhsTitle, "title")) + return Matcher.ComparisonResult(results) + + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsFirstbuttontapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsFirstbuttontapped, let rhsType)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlertmessage, rhs: rhsAlertmessage, with: matcher), lhsAlertmessage, rhsAlertmessage, "alertMessage")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPositiveaction, rhs: rhsPositiveaction, with: matcher), lhsPositiveaction, rhsPositiveaction, "positiveAction")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsOnclosetapped, rhs: rhsOnclosetapped, with: matcher), lhsOnclosetapped, rhsOnclosetapped, "onCloseTapped")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFirstbuttontapped, rhs: rhsFirstbuttontapped, with: matcher), lhsFirstbuttontapped, rhsFirstbuttontapped, "firstButtonTapped")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsType, rhs: rhsType, with: matcher), lhsType, rhsType, "type")) + return Matcher.ComparisonResult(results) + + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappednextSectionTapped_nextSectionTapped(let lhsAlerttitle, let lhsAlertmessage, let lhsNextsectionname, let lhsAction, let lhsImage, let lhsOnclosetapped, let lhsFirstbuttontapped, let lhsNextsectiontapped), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappednextSectionTapped_nextSectionTapped(let rhsAlerttitle, let rhsAlertmessage, let rhsNextsectionname, let rhsAction, let rhsImage, let rhsOnclosetapped, let rhsFirstbuttontapped, let rhsNextsectiontapped)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlertmessage, rhs: rhsAlertmessage, with: matcher), lhsAlertmessage, rhsAlertmessage, "alertMessage")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsNextsectionname, rhs: rhsNextsectionname, with: matcher), lhsNextsectionname, rhsNextsectionname, "nextSectionName")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAction, rhs: rhsAction, with: matcher), lhsAction, rhsAction, "action")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsImage, rhs: rhsImage, with: matcher), lhsImage, rhsImage, "image")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsOnclosetapped, rhs: rhsOnclosetapped, with: matcher), lhsOnclosetapped, rhsOnclosetapped, "onCloseTapped")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFirstbuttontapped, rhs: rhsFirstbuttontapped, with: matcher), lhsFirstbuttontapped, rhsFirstbuttontapped, "firstButtonTapped")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsNextsectiontapped, rhs: rhsNextsectiontapped, with: matcher), lhsNextsectiontapped, rhsNextsectiontapped, "nextSectionTapped")) + return Matcher.ComparisonResult(results) + + case (.m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(let lhsTransitionstyle, let lhsView, let lhsCompletion), .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(let rhsTransitionstyle, let rhsView, let rhsCompletion)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTransitionstyle, rhs: rhsTransitionstyle, with: matcher), lhsTransitionstyle, rhsTransitionstyle, "transitionStyle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsView, rhs: rhsView, with: matcher), lhsView, rhsView, "view")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCompletion, rhs: rhsCompletion, with: matcher), lhsCompletion, rhsCompletion, "completion")) + return Matcher.ComparisonResult(results) + + case (.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let lhsTransitionstyle, let lhsAnimated, let lhsContent), .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let rhsTransitionstyle, let rhsAnimated, let rhsContent)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTransitionstyle, rhs: rhsTransitionstyle, with: matcher), lhsTransitionstyle, rhsTransitionstyle, "transitionStyle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsContent, rhs: rhsContent, with: matcher), lhsContent, rhsContent, "content")) + return Matcher.ComparisonResult(results) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_backToRoot__animated_animated(p0): return p0.intValue + case let .m_back__animated_animated(p0): return p0.intValue + case .m_backWithFade: return 0 + case let .m_dismiss__animated_animated(p0): return p0.intValue + case let .m_removeLastView__controllers_controllers(p0): return p0.intValue + case let .m_showMainOrWhatsNewScreen__sourceScreen_sourceScreenpostLoginData_postLoginData(p0, p1): return p0.intValue + p1.intValue + case .m_showStartupScreen: return 0 + case let .m_showLoginScreen__sourceScreen_sourceScreen(p0): return p0.intValue + case let .m_showRegisterScreen__sourceScreen_sourceScreen(p0): return p0.intValue + case .m_showForgotPasswordScreen: return 0 + case let .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(p0, p1): return p0.intValue + p1.intValue + case let .m_showWebBrowser__title_titleurl_url(p0, p1): return p0.intValue + p1.intValue + case let .m_showSSOWebBrowser__title_title(p0): return p0.intValue + case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue + case let .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue + case let .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue + } + } + func assertionName() -> String { + switch self { + case .m_backToRoot__animated_animated: return ".backToRoot(animated:)" + case .m_back__animated_animated: return ".back(animated:)" + case .m_backWithFade: return ".backWithFade()" + case .m_dismiss__animated_animated: return ".dismiss(animated:)" + case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" + case .m_showMainOrWhatsNewScreen__sourceScreen_sourceScreenpostLoginData_postLoginData: return ".showMainOrWhatsNewScreen(sourceScreen:postLoginData:)" + case .m_showStartupScreen: return ".showStartupScreen()" + case .m_showLoginScreen__sourceScreen_sourceScreen: return ".showLoginScreen(sourceScreen:)" + case .m_showRegisterScreen__sourceScreen_sourceScreen: return ".showRegisterScreen(sourceScreen:)" + case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" + case .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen: return ".showDiscoveryScreen(searchQuery:sourceScreen:)" + case .m_showWebBrowser__title_titleurl_url: return ".showWebBrowser(title:url:)" + case .m_showSSOWebBrowser__title_title: return ".showSSOWebBrowser(title:)" + case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:firstButtonTapped:type:)" + case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:firstButtonTapped:nextSectionTapped:)" + case .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion: return ".presentView(transitionStyle:view:completion:)" + case .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content: return ".presentView(transitionStyle:animated:content:)" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + + } + + public struct Verify { + fileprivate var method: MethodType + + public static func backToRoot(animated: Parameter) -> Verify { return Verify(method: .m_backToRoot__animated_animated(`animated`))} + public static func back(animated: Parameter) -> Verify { return Verify(method: .m_back__animated_animated(`animated`))} + public static func backWithFade() -> Verify { return Verify(method: .m_backWithFade)} + public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} + public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} + public static func showMainOrWhatsNewScreen(sourceScreen: Parameter, postLoginData: Parameter) -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen__sourceScreen_sourceScreenpostLoginData_postLoginData(`sourceScreen`, `postLoginData`))} + public static func showStartupScreen() -> Verify { return Verify(method: .m_showStartupScreen)} + public static func showLoginScreen(sourceScreen: Parameter) -> Verify { return Verify(method: .m_showLoginScreen__sourceScreen_sourceScreen(`sourceScreen`))} + public static func showRegisterScreen(sourceScreen: Parameter) -> Verify { return Verify(method: .m_showRegisterScreen__sourceScreen_sourceScreen(`sourceScreen`))} + public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} + public static func showDiscoveryScreen(searchQuery: Parameter, sourceScreen: Parameter) -> Verify { return Verify(method: .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(`searchQuery`, `sourceScreen`))} + public static func showWebBrowser(title: Parameter, url: Parameter) -> Verify { return Verify(method: .m_showWebBrowser__title_titleurl_url(`title`, `url`))} + public static func showSSOWebBrowser(title: Parameter) -> Verify { return Verify(method: .m_showSSOWebBrowser__title_title(`title`))} + public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, firstButtonTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `firstButtonTapped`, `type`))} + public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, firstButtonTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `firstButtonTapped`, `nextSectionTapped`))} + public static func presentView(transitionStyle: Parameter, view: Parameter, completion: Parameter<(() -> Void)?>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(`transitionStyle`, `view`, `completion`))} + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`))} + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func backToRoot(animated: Parameter, perform: @escaping (Bool) -> Void) -> Perform { + return Perform(method: .m_backToRoot__animated_animated(`animated`), performs: perform) + } + public static func back(animated: Parameter, perform: @escaping (Bool) -> Void) -> Perform { + return Perform(method: .m_back__animated_animated(`animated`), performs: perform) + } + public static func backWithFade(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_backWithFade, performs: perform) + } + public static func dismiss(animated: Parameter, perform: @escaping (Bool) -> Void) -> Perform { + return Perform(method: .m_dismiss__animated_animated(`animated`), performs: perform) + } + public static func removeLastView(controllers: Parameter, perform: @escaping (Int) -> Void) -> Perform { + return Perform(method: .m_removeLastView__controllers_controllers(`controllers`), performs: perform) + } + public static func showMainOrWhatsNewScreen(sourceScreen: Parameter, postLoginData: Parameter, perform: @escaping (LogistrationSourceScreen, PostLoginData?) -> Void) -> Perform { + return Perform(method: .m_showMainOrWhatsNewScreen__sourceScreen_sourceScreenpostLoginData_postLoginData(`sourceScreen`, `postLoginData`), performs: perform) + } + public static func showStartupScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showStartupScreen, performs: perform) + } + public static func showLoginScreen(sourceScreen: Parameter, perform: @escaping (LogistrationSourceScreen) -> Void) -> Perform { + return Perform(method: .m_showLoginScreen__sourceScreen_sourceScreen(`sourceScreen`), performs: perform) + } + public static func showRegisterScreen(sourceScreen: Parameter, perform: @escaping (LogistrationSourceScreen) -> Void) -> Perform { + return Perform(method: .m_showRegisterScreen__sourceScreen_sourceScreen(`sourceScreen`), performs: perform) + } + public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showForgotPasswordScreen, performs: perform) + } + public static func showDiscoveryScreen(searchQuery: Parameter, sourceScreen: Parameter, perform: @escaping (String?, LogistrationSourceScreen) -> Void) -> Perform { + return Perform(method: .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(`searchQuery`, `sourceScreen`), performs: perform) + } + public static func showWebBrowser(title: Parameter, url: Parameter, perform: @escaping (String, URL) -> Void) -> Perform { + return Perform(method: .m_showWebBrowser__title_titleurl_url(`title`, `url`), performs: perform) + } + public static func showSSOWebBrowser(title: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_showSSOWebBrowser__title_title(`title`), performs: perform) + } + public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, firstButtonTapped: Parameter<() -> Void>, type: Parameter, perform: @escaping (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void) -> Perform { + return Perform(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `firstButtonTapped`, `type`), performs: perform) + } + public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, firstButtonTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>, perform: @escaping (String, String, String?, String, SwiftUI.Image, @escaping () -> Void, @escaping () -> Void, @escaping () -> Void) -> Void) -> Perform { + return Perform(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedfirstButtonTapped_firstButtonTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `firstButtonTapped`, `nextSectionTapped`), performs: perform) + } + public static func presentView(transitionStyle: Parameter, view: Parameter, completion: Parameter<(() -> Void)?>, perform: @escaping (UIModalTransitionStyle, any View, (() -> Void)?) -> Void) -> Perform { + return Perform(method: .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(`transitionStyle`, `view`, `completion`), performs: perform) + } + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, Bool, () -> any View) -> Void) -> Perform { + return Perform(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - CalendarManagerProtocol +@MainActor +open class CalendarManagerProtocolMock: CalendarManagerProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + + + + + open func createCalendarIfNeeded() { + addInvocation(.m_createCalendarIfNeeded) + let perform = methodPerformValue(.m_createCalendarIfNeeded) as? () -> Void + perform?() + } + + open func filterCoursesBySelected(fetchedCourses: [CourseForSync]) -> [CourseForSync] { + addInvocation(.m_filterCoursesBySelected__fetchedCourses_fetchedCourses(Parameter<[CourseForSync]>.value(`fetchedCourses`))) + let perform = methodPerformValue(.m_filterCoursesBySelected__fetchedCourses_fetchedCourses(Parameter<[CourseForSync]>.value(`fetchedCourses`))) as? ([CourseForSync]) -> Void + perform?(`fetchedCourses`) + var __value: [CourseForSync] + do { + __value = try methodReturnValue(.m_filterCoursesBySelected__fetchedCourses_fetchedCourses(Parameter<[CourseForSync]>.value(`fetchedCourses`))).casted() + } catch { + onFatalFailure("Stub return value not specified for filterCoursesBySelected(fetchedCourses: [CourseForSync]). Use given") + Failure("Stub return value not specified for filterCoursesBySelected(fetchedCourses: [CourseForSync]). Use given") + } + return __value + } + + open func removeOldCalendar() { + addInvocation(.m_removeOldCalendar) + let perform = methodPerformValue(.m_removeOldCalendar) as? () -> Void + perform?() + } + + open func removeOutdatedEvents(courseID: String) { + addInvocation(.m_removeOutdatedEvents__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_removeOutdatedEvents__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + } + + open func syncCourse(courseID: String, courseName: String, dates: CourseDates) { + addInvocation(.m_syncCourse__courseID_courseIDcourseName_courseNamedates_dates(Parameter.value(`courseID`), Parameter.value(`courseName`), Parameter.value(`dates`))) + let perform = methodPerformValue(.m_syncCourse__courseID_courseIDcourseName_courseNamedates_dates(Parameter.value(`courseID`), Parameter.value(`courseName`), Parameter.value(`dates`))) as? (String, String, CourseDates) -> Void + perform?(`courseID`, `courseName`, `dates`) + } + + open func requestAccess() -> Bool { + addInvocation(.m_requestAccess) + let perform = methodPerformValue(.m_requestAccess) as? () -> Void + perform?() + var __value: Bool + do { + __value = try methodReturnValue(.m_requestAccess).casted() + } catch { + onFatalFailure("Stub return value not specified for requestAccess(). Use given") + Failure("Stub return value not specified for requestAccess(). Use given") + } + return __value + } + + open func courseStatus(courseID: String) -> SyncStatus { + addInvocation(.m_courseStatus__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_courseStatus__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + var __value: SyncStatus + do { + __value = try methodReturnValue(.m_courseStatus__courseID_courseID(Parameter.value(`courseID`))).casted() + } catch { + onFatalFailure("Stub return value not specified for courseStatus(courseID: String). Use given") + Failure("Stub return value not specified for courseStatus(courseID: String). Use given") + } + return __value + } + + open func clearAllData(removeCalendar: Bool) { + addInvocation(.m_clearAllData__removeCalendar_removeCalendar(Parameter.value(`removeCalendar`))) + let perform = methodPerformValue(.m_clearAllData__removeCalendar_removeCalendar(Parameter.value(`removeCalendar`))) as? (Bool) -> Void + perform?(`removeCalendar`) + } + + open func isDatesChanged(courseID: String, checksum: String) -> Bool { + addInvocation(.m_isDatesChanged__courseID_courseIDchecksum_checksum(Parameter.value(`courseID`), Parameter.value(`checksum`))) + let perform = methodPerformValue(.m_isDatesChanged__courseID_courseIDchecksum_checksum(Parameter.value(`courseID`), Parameter.value(`checksum`))) as? (String, String) -> Void + perform?(`courseID`, `checksum`) + var __value: Bool + do { + __value = try methodReturnValue(.m_isDatesChanged__courseID_courseIDchecksum_checksum(Parameter.value(`courseID`), Parameter.value(`checksum`))).casted() + } catch { + onFatalFailure("Stub return value not specified for isDatesChanged(courseID: String, checksum: String). Use given") + Failure("Stub return value not specified for isDatesChanged(courseID: String, checksum: String). Use given") + } + return __value + } + + + fileprivate enum MethodType { + case m_createCalendarIfNeeded + case m_filterCoursesBySelected__fetchedCourses_fetchedCourses(Parameter<[CourseForSync]>) + case m_removeOldCalendar + case m_removeOutdatedEvents__courseID_courseID(Parameter) + case m_syncCourse__courseID_courseIDcourseName_courseNamedates_dates(Parameter, Parameter, Parameter) + case m_requestAccess + case m_courseStatus__courseID_courseID(Parameter) + case m_clearAllData__removeCalendar_removeCalendar(Parameter) + case m_isDatesChanged__courseID_courseIDchecksum_checksum(Parameter, Parameter) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_createCalendarIfNeeded, .m_createCalendarIfNeeded): return .match + + case (.m_filterCoursesBySelected__fetchedCourses_fetchedCourses(let lhsFetchedcourses), .m_filterCoursesBySelected__fetchedCourses_fetchedCourses(let rhsFetchedcourses)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFetchedcourses, rhs: rhsFetchedcourses, with: matcher), lhsFetchedcourses, rhsFetchedcourses, "fetchedCourses")) + return Matcher.ComparisonResult(results) + + case (.m_removeOldCalendar, .m_removeOldCalendar): return .match + + case (.m_removeOutdatedEvents__courseID_courseID(let lhsCourseid), .m_removeOutdatedEvents__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) + + case (.m_syncCourse__courseID_courseIDcourseName_courseNamedates_dates(let lhsCourseid, let lhsCoursename, let lhsDates), .m_syncCourse__courseID_courseIDcourseName_courseNamedates_dates(let rhsCourseid, let rhsCoursename, let rhsDates)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCoursename, rhs: rhsCoursename, with: matcher), lhsCoursename, rhsCoursename, "courseName")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsDates, rhs: rhsDates, with: matcher), lhsDates, rhsDates, "dates")) + return Matcher.ComparisonResult(results) + + case (.m_requestAccess, .m_requestAccess): return .match + + case (.m_courseStatus__courseID_courseID(let lhsCourseid), .m_courseStatus__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) + + case (.m_clearAllData__removeCalendar_removeCalendar(let lhsRemovecalendar), .m_clearAllData__removeCalendar_removeCalendar(let rhsRemovecalendar)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRemovecalendar, rhs: rhsRemovecalendar, with: matcher), lhsRemovecalendar, rhsRemovecalendar, "removeCalendar")) + return Matcher.ComparisonResult(results) + + case (.m_isDatesChanged__courseID_courseIDchecksum_checksum(let lhsCourseid, let lhsChecksum), .m_isDatesChanged__courseID_courseIDchecksum_checksum(let rhsCourseid, let rhsChecksum)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsChecksum, rhs: rhsChecksum, with: matcher), lhsChecksum, rhsChecksum, "checksum")) + return Matcher.ComparisonResult(results) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case .m_createCalendarIfNeeded: return 0 + case let .m_filterCoursesBySelected__fetchedCourses_fetchedCourses(p0): return p0.intValue + case .m_removeOldCalendar: return 0 + case let .m_removeOutdatedEvents__courseID_courseID(p0): return p0.intValue + case let .m_syncCourse__courseID_courseIDcourseName_courseNamedates_dates(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue + case .m_requestAccess: return 0 + case let .m_courseStatus__courseID_courseID(p0): return p0.intValue + case let .m_clearAllData__removeCalendar_removeCalendar(p0): return p0.intValue + case let .m_isDatesChanged__courseID_courseIDchecksum_checksum(p0, p1): return p0.intValue + p1.intValue + } + } + func assertionName() -> String { + switch self { + case .m_createCalendarIfNeeded: return ".createCalendarIfNeeded()" + case .m_filterCoursesBySelected__fetchedCourses_fetchedCourses: return ".filterCoursesBySelected(fetchedCourses:)" + case .m_removeOldCalendar: return ".removeOldCalendar()" + case .m_removeOutdatedEvents__courseID_courseID: return ".removeOutdatedEvents(courseID:)" + case .m_syncCourse__courseID_courseIDcourseName_courseNamedates_dates: return ".syncCourse(courseID:courseName:dates:)" + case .m_requestAccess: return ".requestAccess()" + case .m_courseStatus__courseID_courseID: return ".courseStatus(courseID:)" + case .m_clearAllData__removeCalendar_removeCalendar: return ".clearAllData(removeCalendar:)" + case .m_isDatesChanged__courseID_courseIDchecksum_checksum: return ".isDatesChanged(courseID:checksum:)" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + + public static func filterCoursesBySelected(fetchedCourses: Parameter<[CourseForSync]>, willReturn: [CourseForSync]...) -> MethodStub { + return Given(method: .m_filterCoursesBySelected__fetchedCourses_fetchedCourses(`fetchedCourses`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func requestAccess(willReturn: Bool...) -> MethodStub { + return Given(method: .m_requestAccess, products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func courseStatus(courseID: Parameter, willReturn: SyncStatus...) -> MethodStub { + return Given(method: .m_courseStatus__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func isDatesChanged(courseID: Parameter, checksum: Parameter, willReturn: Bool...) -> MethodStub { + return Given(method: .m_isDatesChanged__courseID_courseIDchecksum_checksum(`courseID`, `checksum`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func filterCoursesBySelected(fetchedCourses: Parameter<[CourseForSync]>, willProduce: (Stubber<[CourseForSync]>) -> Void) -> MethodStub { + let willReturn: [[CourseForSync]] = [] + let given: Given = { return Given(method: .m_filterCoursesBySelected__fetchedCourses_fetchedCourses(`fetchedCourses`), products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: ([CourseForSync]).self) + willProduce(stubber) + return given + } + public static func requestAccess(willProduce: (Stubber) -> Void) -> MethodStub { + let willReturn: [Bool] = [] + let given: Given = { return Given(method: .m_requestAccess, products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (Bool).self) + willProduce(stubber) + return given + } + public static func courseStatus(courseID: Parameter, willProduce: (Stubber) -> Void) -> MethodStub { + let willReturn: [SyncStatus] = [] + let given: Given = { return Given(method: .m_courseStatus__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (SyncStatus).self) + willProduce(stubber) + return given + } + public static func isDatesChanged(courseID: Parameter, checksum: Parameter, willProduce: (Stubber) -> Void) -> MethodStub { + let willReturn: [Bool] = [] + let given: Given = { return Given(method: .m_isDatesChanged__courseID_courseIDchecksum_checksum(`courseID`, `checksum`), products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (Bool).self) + willProduce(stubber) + return given + } + } + + public struct Verify { + fileprivate var method: MethodType + + public static func createCalendarIfNeeded() -> Verify { return Verify(method: .m_createCalendarIfNeeded)} + public static func filterCoursesBySelected(fetchedCourses: Parameter<[CourseForSync]>) -> Verify { return Verify(method: .m_filterCoursesBySelected__fetchedCourses_fetchedCourses(`fetchedCourses`))} + public static func removeOldCalendar() -> Verify { return Verify(method: .m_removeOldCalendar)} + public static func removeOutdatedEvents(courseID: Parameter) -> Verify { return Verify(method: .m_removeOutdatedEvents__courseID_courseID(`courseID`))} + public static func syncCourse(courseID: Parameter, courseName: Parameter, dates: Parameter) -> Verify { return Verify(method: .m_syncCourse__courseID_courseIDcourseName_courseNamedates_dates(`courseID`, `courseName`, `dates`))} + public static func requestAccess() -> Verify { return Verify(method: .m_requestAccess)} + public static func courseStatus(courseID: Parameter) -> Verify { return Verify(method: .m_courseStatus__courseID_courseID(`courseID`))} + public static func clearAllData(removeCalendar: Parameter) -> Verify { return Verify(method: .m_clearAllData__removeCalendar_removeCalendar(`removeCalendar`))} + public static func isDatesChanged(courseID: Parameter, checksum: Parameter) -> Verify { return Verify(method: .m_isDatesChanged__courseID_courseIDchecksum_checksum(`courseID`, `checksum`))} + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func createCalendarIfNeeded(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_createCalendarIfNeeded, performs: perform) + } + public static func filterCoursesBySelected(fetchedCourses: Parameter<[CourseForSync]>, perform: @escaping ([CourseForSync]) -> Void) -> Perform { + return Perform(method: .m_filterCoursesBySelected__fetchedCourses_fetchedCourses(`fetchedCourses`), performs: perform) + } + public static func removeOldCalendar(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_removeOldCalendar, performs: perform) + } + public static func removeOutdatedEvents(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_removeOutdatedEvents__courseID_courseID(`courseID`), performs: perform) + } + public static func syncCourse(courseID: Parameter, courseName: Parameter, dates: Parameter, perform: @escaping (String, String, CourseDates) -> Void) -> Perform { + return Perform(method: .m_syncCourse__courseID_courseIDcourseName_courseNamedates_dates(`courseID`, `courseName`, `dates`), performs: perform) + } + public static func requestAccess(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_requestAccess, performs: perform) + } + public static func courseStatus(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_courseStatus__courseID_courseID(`courseID`), performs: perform) + } + public static func clearAllData(removeCalendar: Parameter, perform: @escaping (Bool) -> Void) -> Perform { + return Perform(method: .m_clearAllData__removeCalendar_removeCalendar(`removeCalendar`), performs: perform) + } + public static func isDatesChanged(courseID: Parameter, checksum: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_isDatesChanged__courseID_courseIDchecksum_checksum(`courseID`, `checksum`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - ConfigProtocol + +open class ConfigProtocolMock: ConfigProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + public var baseURL: URL { + get { invocations.append(.p_baseURL_get); return __p_baseURL ?? givenGetterValue(.p_baseURL_get, "ConfigProtocolMock - stub value for baseURL was not defined") } + } + private var __p_baseURL: (URL)? + + public var baseSSOURL: URL { + get { invocations.append(.p_baseSSOURL_get); return __p_baseSSOURL ?? givenGetterValue(.p_baseSSOURL_get, "ConfigProtocolMock - stub value for baseSSOURL was not defined") } + } + private var __p_baseSSOURL: (URL)? + + public var ssoFinishedURL: URL { + get { invocations.append(.p_ssoFinishedURL_get); return __p_ssoFinishedURL ?? givenGetterValue(.p_ssoFinishedURL_get, "ConfigProtocolMock - stub value for ssoFinishedURL was not defined") } + } + private var __p_ssoFinishedURL: (URL)? + + public var ssoButtonTitle: [String: Any] { + get { invocations.append(.p_ssoButtonTitle_get); return __p_ssoButtonTitle ?? givenGetterValue(.p_ssoButtonTitle_get, "ConfigProtocolMock - stub value for ssoButtonTitle was not defined") } + } + private var __p_ssoButtonTitle: ([String: Any])? + + public var oAuthClientId: String { + get { invocations.append(.p_oAuthClientId_get); return __p_oAuthClientId ?? givenGetterValue(.p_oAuthClientId_get, "ConfigProtocolMock - stub value for oAuthClientId was not defined") } + } + private var __p_oAuthClientId: (String)? + + public var tokenType: TokenType { + get { invocations.append(.p_tokenType_get); return __p_tokenType ?? givenGetterValue(.p_tokenType_get, "ConfigProtocolMock - stub value for tokenType was not defined") } + } + private var __p_tokenType: (TokenType)? + + public var feedbackEmail: String { + get { invocations.append(.p_feedbackEmail_get); return __p_feedbackEmail ?? givenGetterValue(.p_feedbackEmail_get, "ConfigProtocolMock - stub value for feedbackEmail was not defined") } + } + private var __p_feedbackEmail: (String)? + + public var appStoreLink: String { + get { invocations.append(.p_appStoreLink_get); return __p_appStoreLink ?? givenGetterValue(.p_appStoreLink_get, "ConfigProtocolMock - stub value for appStoreLink was not defined") } + } + private var __p_appStoreLink: (String)? + + public var faq: URL? { + get { invocations.append(.p_faq_get); return __p_faq ?? optionalGivenGetterValue(.p_faq_get, "ConfigProtocolMock - stub value for faq was not defined") } + } + private var __p_faq: (URL)? + + public var platformName: String { + get { invocations.append(.p_platformName_get); return __p_platformName ?? givenGetterValue(.p_platformName_get, "ConfigProtocolMock - stub value for platformName was not defined") } + } + private var __p_platformName: (String)? + + public var agreement: AgreementConfig { + get { invocations.append(.p_agreement_get); return __p_agreement ?? givenGetterValue(.p_agreement_get, "ConfigProtocolMock - stub value for agreement was not defined") } + } + private var __p_agreement: (AgreementConfig)? + + public var firebase: FirebaseConfig { + get { invocations.append(.p_firebase_get); return __p_firebase ?? givenGetterValue(.p_firebase_get, "ConfigProtocolMock - stub value for firebase was not defined") } + } + private var __p_firebase: (FirebaseConfig)? + + public var facebook: FacebookConfig { + get { invocations.append(.p_facebook_get); return __p_facebook ?? givenGetterValue(.p_facebook_get, "ConfigProtocolMock - stub value for facebook was not defined") } + } + private var __p_facebook: (FacebookConfig)? + + public var microsoft: MicrosoftConfig { + get { invocations.append(.p_microsoft_get); return __p_microsoft ?? givenGetterValue(.p_microsoft_get, "ConfigProtocolMock - stub value for microsoft was not defined") } + } + private var __p_microsoft: (MicrosoftConfig)? + + public var google: GoogleConfig { + get { invocations.append(.p_google_get); return __p_google ?? givenGetterValue(.p_google_get, "ConfigProtocolMock - stub value for google was not defined") } + } + private var __p_google: (GoogleConfig)? + + public var appleSignIn: AppleSignInConfig { + get { invocations.append(.p_appleSignIn_get); return __p_appleSignIn ?? givenGetterValue(.p_appleSignIn_get, "ConfigProtocolMock - stub value for appleSignIn was not defined") } + } + private var __p_appleSignIn: (AppleSignInConfig)? + + public var features: FeaturesConfig { + get { invocations.append(.p_features_get); return __p_features ?? givenGetterValue(.p_features_get, "ConfigProtocolMock - stub value for features was not defined") } + } + private var __p_features: (FeaturesConfig)? + + public var theme: ThemeConfig { + get { invocations.append(.p_theme_get); return __p_theme ?? givenGetterValue(.p_theme_get, "ConfigProtocolMock - stub value for theme was not defined") } + } + private var __p_theme: (ThemeConfig)? + + public var uiComponents: UIComponentsConfig { + get { invocations.append(.p_uiComponents_get); return __p_uiComponents ?? givenGetterValue(.p_uiComponents_get, "ConfigProtocolMock - stub value for uiComponents was not defined") } + } + private var __p_uiComponents: (UIComponentsConfig)? + + public var discovery: DiscoveryConfig { + get { invocations.append(.p_discovery_get); return __p_discovery ?? givenGetterValue(.p_discovery_get, "ConfigProtocolMock - stub value for discovery was not defined") } + } + private var __p_discovery: (DiscoveryConfig)? + + public var dashboard: DashboardConfig { + get { invocations.append(.p_dashboard_get); return __p_dashboard ?? givenGetterValue(.p_dashboard_get, "ConfigProtocolMock - stub value for dashboard was not defined") } + } + private var __p_dashboard: (DashboardConfig)? + + public var braze: BrazeConfig { + get { invocations.append(.p_braze_get); return __p_braze ?? givenGetterValue(.p_braze_get, "ConfigProtocolMock - stub value for braze was not defined") } + } + private var __p_braze: (BrazeConfig)? + + public var branch: BranchConfig { + get { invocations.append(.p_branch_get); return __p_branch ?? givenGetterValue(.p_branch_get, "ConfigProtocolMock - stub value for branch was not defined") } + } + private var __p_branch: (BranchConfig)? + + public var program: DiscoveryConfig { + get { invocations.append(.p_program_get); return __p_program ?? givenGetterValue(.p_program_get, "ConfigProtocolMock - stub value for program was not defined") } + } + private var __p_program: (DiscoveryConfig)? + + public var experimentalFeatures: ExperimentalFeaturesConfig { + get { invocations.append(.p_experimentalFeatures_get); return __p_experimentalFeatures ?? givenGetterValue(.p_experimentalFeatures_get, "ConfigProtocolMock - stub value for experimentalFeatures was not defined") } + } + private var __p_experimentalFeatures: (ExperimentalFeaturesConfig)? + + public var URIScheme: String { + get { invocations.append(.p_URIScheme_get); return __p_URIScheme ?? givenGetterValue(.p_URIScheme_get, "ConfigProtocolMock - stub value for URIScheme was not defined") } + } + private var __p_URIScheme: (String)? + + + + + + + fileprivate enum MethodType { + case p_baseURL_get + case p_baseSSOURL_get + case p_ssoFinishedURL_get + case p_ssoButtonTitle_get + case p_oAuthClientId_get + case p_tokenType_get + case p_feedbackEmail_get + case p_appStoreLink_get + case p_faq_get + case p_platformName_get + case p_agreement_get + case p_firebase_get + case p_facebook_get + case p_microsoft_get + case p_google_get + case p_appleSignIn_get + case p_features_get + case p_theme_get + case p_uiComponents_get + case p_discovery_get + case p_dashboard_get + case p_braze_get + case p_branch_get + case p_program_get + case p_experimentalFeatures_get + case p_URIScheme_get + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { case (.p_baseURL_get,.p_baseURL_get): return Matcher.ComparisonResult.match + case (.p_baseSSOURL_get,.p_baseSSOURL_get): return Matcher.ComparisonResult.match + case (.p_ssoFinishedURL_get,.p_ssoFinishedURL_get): return Matcher.ComparisonResult.match + case (.p_ssoButtonTitle_get,.p_ssoButtonTitle_get): return Matcher.ComparisonResult.match + case (.p_oAuthClientId_get,.p_oAuthClientId_get): return Matcher.ComparisonResult.match + case (.p_tokenType_get,.p_tokenType_get): return Matcher.ComparisonResult.match + case (.p_feedbackEmail_get,.p_feedbackEmail_get): return Matcher.ComparisonResult.match + case (.p_appStoreLink_get,.p_appStoreLink_get): return Matcher.ComparisonResult.match + case (.p_faq_get,.p_faq_get): return Matcher.ComparisonResult.match + case (.p_platformName_get,.p_platformName_get): return Matcher.ComparisonResult.match + case (.p_agreement_get,.p_agreement_get): return Matcher.ComparisonResult.match + case (.p_firebase_get,.p_firebase_get): return Matcher.ComparisonResult.match + case (.p_facebook_get,.p_facebook_get): return Matcher.ComparisonResult.match + case (.p_microsoft_get,.p_microsoft_get): return Matcher.ComparisonResult.match + case (.p_google_get,.p_google_get): return Matcher.ComparisonResult.match + case (.p_appleSignIn_get,.p_appleSignIn_get): return Matcher.ComparisonResult.match + case (.p_features_get,.p_features_get): return Matcher.ComparisonResult.match + case (.p_theme_get,.p_theme_get): return Matcher.ComparisonResult.match + case (.p_uiComponents_get,.p_uiComponents_get): return Matcher.ComparisonResult.match + case (.p_discovery_get,.p_discovery_get): return Matcher.ComparisonResult.match + case (.p_dashboard_get,.p_dashboard_get): return Matcher.ComparisonResult.match + case (.p_braze_get,.p_braze_get): return Matcher.ComparisonResult.match + case (.p_branch_get,.p_branch_get): return Matcher.ComparisonResult.match + case (.p_program_get,.p_program_get): return Matcher.ComparisonResult.match + case (.p_experimentalFeatures_get,.p_experimentalFeatures_get): return Matcher.ComparisonResult.match + case (.p_URIScheme_get,.p_URIScheme_get): return Matcher.ComparisonResult.match + default: return .none + } + } + + func intValue() -> Int { + switch self { + case .p_baseURL_get: return 0 + case .p_baseSSOURL_get: return 0 + case .p_ssoFinishedURL_get: return 0 + case .p_ssoButtonTitle_get: return 0 + case .p_oAuthClientId_get: return 0 + case .p_tokenType_get: return 0 + case .p_feedbackEmail_get: return 0 + case .p_appStoreLink_get: return 0 + case .p_faq_get: return 0 + case .p_platformName_get: return 0 + case .p_agreement_get: return 0 + case .p_firebase_get: return 0 + case .p_facebook_get: return 0 + case .p_microsoft_get: return 0 + case .p_google_get: return 0 + case .p_appleSignIn_get: return 0 + case .p_features_get: return 0 + case .p_theme_get: return 0 + case .p_uiComponents_get: return 0 + case .p_discovery_get: return 0 + case .p_dashboard_get: return 0 + case .p_braze_get: return 0 + case .p_branch_get: return 0 + case .p_program_get: return 0 + case .p_experimentalFeatures_get: return 0 + case .p_URIScheme_get: return 0 + } + } + func assertionName() -> String { + switch self { + case .p_baseURL_get: return "[get] .baseURL" + case .p_baseSSOURL_get: return "[get] .baseSSOURL" + case .p_ssoFinishedURL_get: return "[get] .ssoFinishedURL" + case .p_ssoButtonTitle_get: return "[get] .ssoButtonTitle" + case .p_oAuthClientId_get: return "[get] .oAuthClientId" + case .p_tokenType_get: return "[get] .tokenType" + case .p_feedbackEmail_get: return "[get] .feedbackEmail" + case .p_appStoreLink_get: return "[get] .appStoreLink" + case .p_faq_get: return "[get] .faq" + case .p_platformName_get: return "[get] .platformName" + case .p_agreement_get: return "[get] .agreement" + case .p_firebase_get: return "[get] .firebase" + case .p_facebook_get: return "[get] .facebook" + case .p_microsoft_get: return "[get] .microsoft" + case .p_google_get: return "[get] .google" + case .p_appleSignIn_get: return "[get] .appleSignIn" + case .p_features_get: return "[get] .features" + case .p_theme_get: return "[get] .theme" + case .p_uiComponents_get: return "[get] .uiComponents" + case .p_discovery_get: return "[get] .discovery" + case .p_dashboard_get: return "[get] .dashboard" + case .p_braze_get: return "[get] .braze" + case .p_branch_get: return "[get] .branch" + case .p_program_get: return "[get] .program" + case .p_experimentalFeatures_get: return "[get] .experimentalFeatures" + case .p_URIScheme_get: return "[get] .URIScheme" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + public static func baseURL(getter defaultValue: URL...) -> PropertyStub { + return Given(method: .p_baseURL_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func baseSSOURL(getter defaultValue: URL...) -> PropertyStub { + return Given(method: .p_baseSSOURL_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func ssoFinishedURL(getter defaultValue: URL...) -> PropertyStub { + return Given(method: .p_ssoFinishedURL_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func ssoButtonTitle(getter defaultValue: [String: Any]...) -> PropertyStub { + return Given(method: .p_ssoButtonTitle_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func oAuthClientId(getter defaultValue: String...) -> PropertyStub { + return Given(method: .p_oAuthClientId_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func tokenType(getter defaultValue: TokenType...) -> PropertyStub { + return Given(method: .p_tokenType_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func feedbackEmail(getter defaultValue: String...) -> PropertyStub { + return Given(method: .p_feedbackEmail_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func appStoreLink(getter defaultValue: String...) -> PropertyStub { + return Given(method: .p_appStoreLink_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func faq(getter defaultValue: URL?...) -> PropertyStub { + return Given(method: .p_faq_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func platformName(getter defaultValue: String...) -> PropertyStub { + return Given(method: .p_platformName_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func agreement(getter defaultValue: AgreementConfig...) -> PropertyStub { + return Given(method: .p_agreement_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func firebase(getter defaultValue: FirebaseConfig...) -> PropertyStub { + return Given(method: .p_firebase_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func facebook(getter defaultValue: FacebookConfig...) -> PropertyStub { + return Given(method: .p_facebook_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func microsoft(getter defaultValue: MicrosoftConfig...) -> PropertyStub { + return Given(method: .p_microsoft_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func google(getter defaultValue: GoogleConfig...) -> PropertyStub { + return Given(method: .p_google_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func appleSignIn(getter defaultValue: AppleSignInConfig...) -> PropertyStub { + return Given(method: .p_appleSignIn_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func features(getter defaultValue: FeaturesConfig...) -> PropertyStub { + return Given(method: .p_features_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func theme(getter defaultValue: ThemeConfig...) -> PropertyStub { + return Given(method: .p_theme_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func uiComponents(getter defaultValue: UIComponentsConfig...) -> PropertyStub { + return Given(method: .p_uiComponents_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func discovery(getter defaultValue: DiscoveryConfig...) -> PropertyStub { + return Given(method: .p_discovery_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func dashboard(getter defaultValue: DashboardConfig...) -> PropertyStub { + return Given(method: .p_dashboard_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func braze(getter defaultValue: BrazeConfig...) -> PropertyStub { + return Given(method: .p_braze_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func branch(getter defaultValue: BranchConfig...) -> PropertyStub { + return Given(method: .p_branch_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func program(getter defaultValue: DiscoveryConfig...) -> PropertyStub { + return Given(method: .p_program_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func experimentalFeatures(getter defaultValue: ExperimentalFeaturesConfig...) -> PropertyStub { + return Given(method: .p_experimentalFeatures_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func URIScheme(getter defaultValue: String...) -> PropertyStub { + return Given(method: .p_URIScheme_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + + } + + public struct Verify { + fileprivate var method: MethodType + + public static var baseURL: Verify { return Verify(method: .p_baseURL_get) } + public static var baseSSOURL: Verify { return Verify(method: .p_baseSSOURL_get) } + public static var ssoFinishedURL: Verify { return Verify(method: .p_ssoFinishedURL_get) } + public static var ssoButtonTitle: Verify { return Verify(method: .p_ssoButtonTitle_get) } + public static var oAuthClientId: Verify { return Verify(method: .p_oAuthClientId_get) } + public static var tokenType: Verify { return Verify(method: .p_tokenType_get) } + public static var feedbackEmail: Verify { return Verify(method: .p_feedbackEmail_get) } + public static var appStoreLink: Verify { return Verify(method: .p_appStoreLink_get) } + public static var faq: Verify { return Verify(method: .p_faq_get) } + public static var platformName: Verify { return Verify(method: .p_platformName_get) } + public static var agreement: Verify { return Verify(method: .p_agreement_get) } + public static var firebase: Verify { return Verify(method: .p_firebase_get) } + public static var facebook: Verify { return Verify(method: .p_facebook_get) } + public static var microsoft: Verify { return Verify(method: .p_microsoft_get) } + public static var google: Verify { return Verify(method: .p_google_get) } + public static var appleSignIn: Verify { return Verify(method: .p_appleSignIn_get) } + public static var features: Verify { return Verify(method: .p_features_get) } + public static var theme: Verify { return Verify(method: .p_theme_get) } + public static var uiComponents: Verify { return Verify(method: .p_uiComponents_get) } + public static var discovery: Verify { return Verify(method: .p_discovery_get) } + public static var dashboard: Verify { return Verify(method: .p_dashboard_get) } + public static var braze: Verify { return Verify(method: .p_braze_get) } + public static var branch: Verify { return Verify(method: .p_branch_get) } + public static var program: Verify { return Verify(method: .p_program_get) } + public static var experimentalFeatures: Verify { return Verify(method: .p_experimentalFeatures_get) } + public static var URIScheme: Verify { return Verify(method: .p_URIScheme_get) } + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - ConnectivityProtocol +@MainActor +open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + public var isInternetAvaliable: Bool { + get { invocations.append(.p_isInternetAvaliable_get); return __p_isInternetAvaliable ?? givenGetterValue(.p_isInternetAvaliable_get, "ConnectivityProtocolMock - stub value for isInternetAvaliable was not defined") } + } + private var __p_isInternetAvaliable: (Bool)? + + public var isMobileData: Bool { + get { invocations.append(.p_isMobileData_get); return __p_isMobileData ?? givenGetterValue(.p_isMobileData_get, "ConnectivityProtocolMock - stub value for isMobileData was not defined") } + } + private var __p_isMobileData: (Bool)? + + public var internetReachableSubject: CurrentValueSubject { + get { invocations.append(.p_internetReachableSubject_get); return __p_internetReachableSubject ?? givenGetterValue(.p_internetReachableSubject_get, "ConnectivityProtocolMock - stub value for internetReachableSubject was not defined") } + } + private var __p_internetReachableSubject: (CurrentValueSubject)? + + + + + + + fileprivate enum MethodType { + case p_isInternetAvaliable_get + case p_isMobileData_get + case p_internetReachableSubject_get + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { case (.p_isInternetAvaliable_get,.p_isInternetAvaliable_get): return Matcher.ComparisonResult.match + case (.p_isMobileData_get,.p_isMobileData_get): return Matcher.ComparisonResult.match + case (.p_internetReachableSubject_get,.p_internetReachableSubject_get): return Matcher.ComparisonResult.match + default: return .none + } + } + + func intValue() -> Int { + switch self { + case .p_isInternetAvaliable_get: return 0 + case .p_isMobileData_get: return 0 + case .p_internetReachableSubject_get: return 0 + } + } + func assertionName() -> String { + switch self { + case .p_isInternetAvaliable_get: return "[get] .isInternetAvaliable" + case .p_isMobileData_get: return "[get] .isMobileData" + case .p_internetReachableSubject_get: return "[get] .internetReachableSubject" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + public static func isInternetAvaliable(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_isInternetAvaliable_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func isMobileData(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_isMobileData_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func internetReachableSubject(getter defaultValue: CurrentValueSubject...) -> PropertyStub { + return Given(method: .p_internetReachableSubject_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + + } + + public struct Verify { + fileprivate var method: MethodType + + public static var isInternetAvaliable: Verify { return Verify(method: .p_isInternetAvaliable_get) } + public static var isMobileData: Verify { return Verify(method: .p_isMobileData_get) } + public static var internetReachableSubject: Verify { return Verify(method: .p_internetReachableSubject_get) } + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - CoreAnalytics + +open class CoreAnalyticsMock: CoreAnalytics, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + + + + + open func trackEvent(_ event: AnalyticsEvent, parameters: [String: Any]?) { + addInvocation(.m_trackEvent__eventparameters_parameters(Parameter.value(`event`), Parameter<[String: Any]?>.value(`parameters`))) + let perform = methodPerformValue(.m_trackEvent__eventparameters_parameters(Parameter.value(`event`), Parameter<[String: Any]?>.value(`parameters`))) as? (AnalyticsEvent, [String: Any]?) -> Void + perform?(`event`, `parameters`) + } + + open func trackEvent(_ event: AnalyticsEvent, biValue: EventBIValue, parameters: [String: Any]?) { + addInvocation(.m_trackEvent__eventbiValue_biValueparameters_parameters(Parameter.value(`event`), Parameter.value(`biValue`), Parameter<[String: Any]?>.value(`parameters`))) + let perform = methodPerformValue(.m_trackEvent__eventbiValue_biValueparameters_parameters(Parameter.value(`event`), Parameter.value(`biValue`), Parameter<[String: Any]?>.value(`parameters`))) as? (AnalyticsEvent, EventBIValue, [String: Any]?) -> Void + perform?(`event`, `biValue`, `parameters`) + } + + open func trackScreenEvent(_ event: AnalyticsEvent, parameters: [String: Any]?) { + addInvocation(.m_trackScreenEvent__eventparameters_parameters(Parameter.value(`event`), Parameter<[String: Any]?>.value(`parameters`))) + let perform = methodPerformValue(.m_trackScreenEvent__eventparameters_parameters(Parameter.value(`event`), Parameter<[String: Any]?>.value(`parameters`))) as? (AnalyticsEvent, [String: Any]?) -> Void + perform?(`event`, `parameters`) + } + + open func trackScreenEvent(_ event: AnalyticsEvent, biValue: EventBIValue, parameters: [String: Any]?) { + addInvocation(.m_trackScreenEvent__eventbiValue_biValueparameters_parameters(Parameter.value(`event`), Parameter.value(`biValue`), Parameter<[String: Any]?>.value(`parameters`))) + let perform = methodPerformValue(.m_trackScreenEvent__eventbiValue_biValueparameters_parameters(Parameter.value(`event`), Parameter.value(`biValue`), Parameter<[String: Any]?>.value(`parameters`))) as? (AnalyticsEvent, EventBIValue, [String: Any]?) -> Void + perform?(`event`, `biValue`, `parameters`) + } + + open func appreview(_ event: AnalyticsEvent, biValue: EventBIValue, action: String?, rating: Int?) { + addInvocation(.m_appreview__eventbiValue_biValueaction_actionrating_rating(Parameter.value(`event`), Parameter.value(`biValue`), Parameter.value(`action`), Parameter.value(`rating`))) + let perform = methodPerformValue(.m_appreview__eventbiValue_biValueaction_actionrating_rating(Parameter.value(`event`), Parameter.value(`biValue`), Parameter.value(`action`), Parameter.value(`rating`))) as? (AnalyticsEvent, EventBIValue, String?, Int?) -> Void + perform?(`event`, `biValue`, `action`, `rating`) + } + + open func videoQualityChanged(_ event: AnalyticsEvent, bivalue: EventBIValue, value: String, oldValue: String) { + addInvocation(.m_videoQualityChanged__eventbivalue_bivaluevalue_valueoldValue_oldValue(Parameter.value(`event`), Parameter.value(`bivalue`), Parameter.value(`value`), Parameter.value(`oldValue`))) + let perform = methodPerformValue(.m_videoQualityChanged__eventbivalue_bivaluevalue_valueoldValue_oldValue(Parameter.value(`event`), Parameter.value(`bivalue`), Parameter.value(`value`), Parameter.value(`oldValue`))) as? (AnalyticsEvent, EventBIValue, String, String) -> Void + perform?(`event`, `bivalue`, `value`, `oldValue`) + } + + open func trackEvent(_ event: AnalyticsEvent) { + addInvocation(.m_trackEvent__event(Parameter.value(`event`))) + let perform = methodPerformValue(.m_trackEvent__event(Parameter.value(`event`))) as? (AnalyticsEvent) -> Void + perform?(`event`) + } + + open func trackEvent(_ event: AnalyticsEvent, biValue: EventBIValue) { + addInvocation(.m_trackEvent__eventbiValue_biValue(Parameter.value(`event`), Parameter.value(`biValue`))) + let perform = methodPerformValue(.m_trackEvent__eventbiValue_biValue(Parameter.value(`event`), Parameter.value(`biValue`))) as? (AnalyticsEvent, EventBIValue) -> Void + perform?(`event`, `biValue`) + } + + open func trackScreenEvent(_ event: AnalyticsEvent) { + addInvocation(.m_trackScreenEvent__event(Parameter.value(`event`))) + let perform = methodPerformValue(.m_trackScreenEvent__event(Parameter.value(`event`))) as? (AnalyticsEvent) -> Void + perform?(`event`) + } + + open func trackScreenEvent(_ event: AnalyticsEvent, biValue: EventBIValue) { + addInvocation(.m_trackScreenEvent__eventbiValue_biValue(Parameter.value(`event`), Parameter.value(`biValue`))) + let perform = methodPerformValue(.m_trackScreenEvent__eventbiValue_biValue(Parameter.value(`event`), Parameter.value(`biValue`))) as? (AnalyticsEvent, EventBIValue) -> Void + perform?(`event`, `biValue`) + } + + + fileprivate enum MethodType { + case m_trackEvent__eventparameters_parameters(Parameter, Parameter<[String: Any]?>) + case m_trackEvent__eventbiValue_biValueparameters_parameters(Parameter, Parameter, Parameter<[String: Any]?>) + case m_trackScreenEvent__eventparameters_parameters(Parameter, Parameter<[String: Any]?>) + case m_trackScreenEvent__eventbiValue_biValueparameters_parameters(Parameter, Parameter, Parameter<[String: Any]?>) + case m_appreview__eventbiValue_biValueaction_actionrating_rating(Parameter, Parameter, Parameter, Parameter) + case m_videoQualityChanged__eventbivalue_bivaluevalue_valueoldValue_oldValue(Parameter, Parameter, Parameter, Parameter) + case m_trackEvent__event(Parameter) + case m_trackEvent__eventbiValue_biValue(Parameter, Parameter) + case m_trackScreenEvent__event(Parameter) + case m_trackScreenEvent__eventbiValue_biValue(Parameter, Parameter) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_trackEvent__eventparameters_parameters(let lhsEvent, let lhsParameters), .m_trackEvent__eventparameters_parameters(let rhsEvent, let rhsParameters)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEvent, rhs: rhsEvent, with: matcher), lhsEvent, rhsEvent, "_ event")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsParameters, rhs: rhsParameters, with: matcher), lhsParameters, rhsParameters, "parameters")) + return Matcher.ComparisonResult(results) + + case (.m_trackEvent__eventbiValue_biValueparameters_parameters(let lhsEvent, let lhsBivalue, let lhsParameters), .m_trackEvent__eventbiValue_biValueparameters_parameters(let rhsEvent, let rhsBivalue, let rhsParameters)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEvent, rhs: rhsEvent, with: matcher), lhsEvent, rhsEvent, "_ event")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBivalue, rhs: rhsBivalue, with: matcher), lhsBivalue, rhsBivalue, "biValue")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsParameters, rhs: rhsParameters, with: matcher), lhsParameters, rhsParameters, "parameters")) + return Matcher.ComparisonResult(results) + + case (.m_trackScreenEvent__eventparameters_parameters(let lhsEvent, let lhsParameters), .m_trackScreenEvent__eventparameters_parameters(let rhsEvent, let rhsParameters)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEvent, rhs: rhsEvent, with: matcher), lhsEvent, rhsEvent, "_ event")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsParameters, rhs: rhsParameters, with: matcher), lhsParameters, rhsParameters, "parameters")) + return Matcher.ComparisonResult(results) + + case (.m_trackScreenEvent__eventbiValue_biValueparameters_parameters(let lhsEvent, let lhsBivalue, let lhsParameters), .m_trackScreenEvent__eventbiValue_biValueparameters_parameters(let rhsEvent, let rhsBivalue, let rhsParameters)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEvent, rhs: rhsEvent, with: matcher), lhsEvent, rhsEvent, "_ event")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBivalue, rhs: rhsBivalue, with: matcher), lhsBivalue, rhsBivalue, "biValue")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsParameters, rhs: rhsParameters, with: matcher), lhsParameters, rhsParameters, "parameters")) + return Matcher.ComparisonResult(results) + + case (.m_appreview__eventbiValue_biValueaction_actionrating_rating(let lhsEvent, let lhsBivalue, let lhsAction, let lhsRating), .m_appreview__eventbiValue_biValueaction_actionrating_rating(let rhsEvent, let rhsBivalue, let rhsAction, let rhsRating)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEvent, rhs: rhsEvent, with: matcher), lhsEvent, rhsEvent, "_ event")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBivalue, rhs: rhsBivalue, with: matcher), lhsBivalue, rhsBivalue, "biValue")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAction, rhs: rhsAction, with: matcher), lhsAction, rhsAction, "action")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRating, rhs: rhsRating, with: matcher), lhsRating, rhsRating, "rating")) + return Matcher.ComparisonResult(results) + + case (.m_videoQualityChanged__eventbivalue_bivaluevalue_valueoldValue_oldValue(let lhsEvent, let lhsBivalue, let lhsValue, let lhsOldvalue), .m_videoQualityChanged__eventbivalue_bivaluevalue_valueoldValue_oldValue(let rhsEvent, let rhsBivalue, let rhsValue, let rhsOldvalue)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEvent, rhs: rhsEvent, with: matcher), lhsEvent, rhsEvent, "_ event")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBivalue, rhs: rhsBivalue, with: matcher), lhsBivalue, rhsBivalue, "bivalue")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsValue, rhs: rhsValue, with: matcher), lhsValue, rhsValue, "value")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsOldvalue, rhs: rhsOldvalue, with: matcher), lhsOldvalue, rhsOldvalue, "oldValue")) + return Matcher.ComparisonResult(results) + + case (.m_trackEvent__event(let lhsEvent), .m_trackEvent__event(let rhsEvent)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEvent, rhs: rhsEvent, with: matcher), lhsEvent, rhsEvent, "_ event")) + return Matcher.ComparisonResult(results) + + case (.m_trackEvent__eventbiValue_biValue(let lhsEvent, let lhsBivalue), .m_trackEvent__eventbiValue_biValue(let rhsEvent, let rhsBivalue)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEvent, rhs: rhsEvent, with: matcher), lhsEvent, rhsEvent, "_ event")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBivalue, rhs: rhsBivalue, with: matcher), lhsBivalue, rhsBivalue, "biValue")) + return Matcher.ComparisonResult(results) + + case (.m_trackScreenEvent__event(let lhsEvent), .m_trackScreenEvent__event(let rhsEvent)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEvent, rhs: rhsEvent, with: matcher), lhsEvent, rhsEvent, "_ event")) + return Matcher.ComparisonResult(results) + + case (.m_trackScreenEvent__eventbiValue_biValue(let lhsEvent, let lhsBivalue), .m_trackScreenEvent__eventbiValue_biValue(let rhsEvent, let rhsBivalue)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEvent, rhs: rhsEvent, with: matcher), lhsEvent, rhsEvent, "_ event")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBivalue, rhs: rhsBivalue, with: matcher), lhsBivalue, rhsBivalue, "biValue")) + return Matcher.ComparisonResult(results) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_trackEvent__eventparameters_parameters(p0, p1): return p0.intValue + p1.intValue + case let .m_trackEvent__eventbiValue_biValueparameters_parameters(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue + case let .m_trackScreenEvent__eventparameters_parameters(p0, p1): return p0.intValue + p1.intValue + case let .m_trackScreenEvent__eventbiValue_biValueparameters_parameters(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue + case let .m_appreview__eventbiValue_biValueaction_actionrating_rating(p0, p1, p2, p3): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + case let .m_videoQualityChanged__eventbivalue_bivaluevalue_valueoldValue_oldValue(p0, p1, p2, p3): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + case let .m_trackEvent__event(p0): return p0.intValue + case let .m_trackEvent__eventbiValue_biValue(p0, p1): return p0.intValue + p1.intValue + case let .m_trackScreenEvent__event(p0): return p0.intValue + case let .m_trackScreenEvent__eventbiValue_biValue(p0, p1): return p0.intValue + p1.intValue + } + } + func assertionName() -> String { + switch self { + case .m_trackEvent__eventparameters_parameters: return ".trackEvent(_:parameters:)" + case .m_trackEvent__eventbiValue_biValueparameters_parameters: return ".trackEvent(_:biValue:parameters:)" + case .m_trackScreenEvent__eventparameters_parameters: return ".trackScreenEvent(_:parameters:)" + case .m_trackScreenEvent__eventbiValue_biValueparameters_parameters: return ".trackScreenEvent(_:biValue:parameters:)" + case .m_appreview__eventbiValue_biValueaction_actionrating_rating: return ".appreview(_:biValue:action:rating:)" + case .m_videoQualityChanged__eventbivalue_bivaluevalue_valueoldValue_oldValue: return ".videoQualityChanged(_:bivalue:value:oldValue:)" + case .m_trackEvent__event: return ".trackEvent(_:)" + case .m_trackEvent__eventbiValue_biValue: return ".trackEvent(_:biValue:)" + case .m_trackScreenEvent__event: return ".trackScreenEvent(_:)" + case .m_trackScreenEvent__eventbiValue_biValue: return ".trackScreenEvent(_:biValue:)" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + + } + + public struct Verify { + fileprivate var method: MethodType + + public static func trackEvent(_ event: Parameter, parameters: Parameter<[String: Any]?>) -> Verify { return Verify(method: .m_trackEvent__eventparameters_parameters(`event`, `parameters`))} + public static func trackEvent(_ event: Parameter, biValue: Parameter, parameters: Parameter<[String: Any]?>) -> Verify { return Verify(method: .m_trackEvent__eventbiValue_biValueparameters_parameters(`event`, `biValue`, `parameters`))} + public static func trackScreenEvent(_ event: Parameter, parameters: Parameter<[String: Any]?>) -> Verify { return Verify(method: .m_trackScreenEvent__eventparameters_parameters(`event`, `parameters`))} + public static func trackScreenEvent(_ event: Parameter, biValue: Parameter, parameters: Parameter<[String: Any]?>) -> Verify { return Verify(method: .m_trackScreenEvent__eventbiValue_biValueparameters_parameters(`event`, `biValue`, `parameters`))} + public static func appreview(_ event: Parameter, biValue: Parameter, action: Parameter, rating: Parameter) -> Verify { return Verify(method: .m_appreview__eventbiValue_biValueaction_actionrating_rating(`event`, `biValue`, `action`, `rating`))} + public static func videoQualityChanged(_ event: Parameter, bivalue: Parameter, value: Parameter, oldValue: Parameter) -> Verify { return Verify(method: .m_videoQualityChanged__eventbivalue_bivaluevalue_valueoldValue_oldValue(`event`, `bivalue`, `value`, `oldValue`))} + public static func trackEvent(_ event: Parameter) -> Verify { return Verify(method: .m_trackEvent__event(`event`))} + public static func trackEvent(_ event: Parameter, biValue: Parameter) -> Verify { return Verify(method: .m_trackEvent__eventbiValue_biValue(`event`, `biValue`))} + public static func trackScreenEvent(_ event: Parameter) -> Verify { return Verify(method: .m_trackScreenEvent__event(`event`))} + public static func trackScreenEvent(_ event: Parameter, biValue: Parameter) -> Verify { return Verify(method: .m_trackScreenEvent__eventbiValue_biValue(`event`, `biValue`))} + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func trackEvent(_ event: Parameter, parameters: Parameter<[String: Any]?>, perform: @escaping (AnalyticsEvent, [String: Any]?) -> Void) -> Perform { + return Perform(method: .m_trackEvent__eventparameters_parameters(`event`, `parameters`), performs: perform) + } + public static func trackEvent(_ event: Parameter, biValue: Parameter, parameters: Parameter<[String: Any]?>, perform: @escaping (AnalyticsEvent, EventBIValue, [String: Any]?) -> Void) -> Perform { + return Perform(method: .m_trackEvent__eventbiValue_biValueparameters_parameters(`event`, `biValue`, `parameters`), performs: perform) + } + public static func trackScreenEvent(_ event: Parameter, parameters: Parameter<[String: Any]?>, perform: @escaping (AnalyticsEvent, [String: Any]?) -> Void) -> Perform { + return Perform(method: .m_trackScreenEvent__eventparameters_parameters(`event`, `parameters`), performs: perform) + } + public static func trackScreenEvent(_ event: Parameter, biValue: Parameter, parameters: Parameter<[String: Any]?>, perform: @escaping (AnalyticsEvent, EventBIValue, [String: Any]?) -> Void) -> Perform { + return Perform(method: .m_trackScreenEvent__eventbiValue_biValueparameters_parameters(`event`, `biValue`, `parameters`), performs: perform) + } + public static func appreview(_ event: Parameter, biValue: Parameter, action: Parameter, rating: Parameter, perform: @escaping (AnalyticsEvent, EventBIValue, String?, Int?) -> Void) -> Perform { + return Perform(method: .m_appreview__eventbiValue_biValueaction_actionrating_rating(`event`, `biValue`, `action`, `rating`), performs: perform) + } + public static func videoQualityChanged(_ event: Parameter, bivalue: Parameter, value: Parameter, oldValue: Parameter, perform: @escaping (AnalyticsEvent, EventBIValue, String, String) -> Void) -> Perform { + return Perform(method: .m_videoQualityChanged__eventbivalue_bivaluevalue_valueoldValue_oldValue(`event`, `bivalue`, `value`, `oldValue`), performs: perform) + } + public static func trackEvent(_ event: Parameter, perform: @escaping (AnalyticsEvent) -> Void) -> Perform { + return Perform(method: .m_trackEvent__event(`event`), performs: perform) + } + public static func trackEvent(_ event: Parameter, biValue: Parameter, perform: @escaping (AnalyticsEvent, EventBIValue) -> Void) -> Perform { + return Perform(method: .m_trackEvent__eventbiValue_biValue(`event`, `biValue`), performs: perform) + } + public static func trackScreenEvent(_ event: Parameter, perform: @escaping (AnalyticsEvent) -> Void) -> Perform { + return Perform(method: .m_trackScreenEvent__event(`event`), performs: perform) + } + public static func trackScreenEvent(_ event: Parameter, biValue: Parameter, perform: @escaping (AnalyticsEvent, EventBIValue) -> Void) -> Perform { + return Perform(method: .m_trackScreenEvent__eventbiValue_biValue(`event`, `biValue`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - CorePersistenceProtocol + +open class CorePersistenceProtocolMock: CorePersistenceProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + + + + + open func set(userId: Int) { + addInvocation(.m_set__userId_userId(Parameter.value(`userId`))) + let perform = methodPerformValue(.m_set__userId_userId(Parameter.value(`userId`))) as? (Int) -> Void + perform?(`userId`) + } + + open func getUserID() -> Int? { + addInvocation(.m_getUserID) + let perform = methodPerformValue(.m_getUserID) as? () -> Void + perform?() + var __value: Int? = nil + do { + __value = try methodReturnValue(.m_getUserID).casted() + } catch { + // do nothing + } + return __value + } + + @MainActor + open func publisher() throws -> AnyPublisher { + addInvocation(.m_publisher) + let perform = methodPerformValue(.m_publisher) as? () -> Void + perform?() + var __value: AnyPublisher + do { + __value = try methodReturnValue(.m_publisher).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for publisher(). Use given") + Failure("Stub return value not specified for publisher(). Use given") + } catch { + throw error + } + return __value + } + + open func addToDownloadQueue(tasks: [DownloadDataTask]) { + addInvocation(.m_addToDownloadQueue__tasks_tasks(Parameter<[DownloadDataTask]>.value(`tasks`))) + let perform = methodPerformValue(.m_addToDownloadQueue__tasks_tasks(Parameter<[DownloadDataTask]>.value(`tasks`))) as? ([DownloadDataTask]) -> Void + perform?(`tasks`) + } + + open func saveOfflineProgress(progress: OfflineProgress) { + addInvocation(.m_saveOfflineProgress__progress_progress(Parameter.value(`progress`))) + let perform = methodPerformValue(.m_saveOfflineProgress__progress_progress(Parameter.value(`progress`))) as? (OfflineProgress) -> Void + perform?(`progress`) + } + + open func loadProgress(for blockID: String) -> OfflineProgress? { + addInvocation(.m_loadProgress__for_blockID(Parameter.value(`blockID`))) + let perform = methodPerformValue(.m_loadProgress__for_blockID(Parameter.value(`blockID`))) as? (String) -> Void + perform?(`blockID`) + var __value: OfflineProgress? = nil + do { + __value = try methodReturnValue(.m_loadProgress__for_blockID(Parameter.value(`blockID`))).casted() + } catch { + // do nothing + } + return __value + } + + open func loadAllOfflineProgress() -> [OfflineProgress] { + addInvocation(.m_loadAllOfflineProgress) + let perform = methodPerformValue(.m_loadAllOfflineProgress) as? () -> Void + perform?() + var __value: [OfflineProgress] + do { + __value = try methodReturnValue(.m_loadAllOfflineProgress).casted() + } catch { + onFatalFailure("Stub return value not specified for loadAllOfflineProgress(). Use given") + Failure("Stub return value not specified for loadAllOfflineProgress(). Use given") + } + return __value + } + + open func deleteProgress(for blockID: String) { + addInvocation(.m_deleteProgress__for_blockID(Parameter.value(`blockID`))) + let perform = methodPerformValue(.m_deleteProgress__for_blockID(Parameter.value(`blockID`))) as? (String) -> Void + perform?(`blockID`) + } + + open func deleteAllProgress() { + addInvocation(.m_deleteAllProgress) + let perform = methodPerformValue(.m_deleteAllProgress) as? () -> Void + perform?() + } + + open func addToDownloadQueue(blocks: [CourseBlock], downloadQuality: DownloadQuality) { + addInvocation(.m_addToDownloadQueue__blocks_blocksdownloadQuality_downloadQuality(Parameter<[CourseBlock]>.value(`blocks`), Parameter.value(`downloadQuality`))) + let perform = methodPerformValue(.m_addToDownloadQueue__blocks_blocksdownloadQuality_downloadQuality(Parameter<[CourseBlock]>.value(`blocks`), Parameter.value(`downloadQuality`))) as? ([CourseBlock], DownloadQuality) -> Void + perform?(`blocks`, `downloadQuality`) + } + + open func updateTask(task: DownloadDataTask) { + addInvocation(.m_updateTask__task_task(Parameter.value(`task`))) + let perform = methodPerformValue(.m_updateTask__task_task(Parameter.value(`task`))) as? (DownloadDataTask) -> Void + perform?(`task`) + } + + open func downloadDataTask(for blockId: String) -> DownloadDataTask? { + addInvocation(.m_downloadDataTask__for_blockId(Parameter.value(`blockId`))) + let perform = methodPerformValue(.m_downloadDataTask__for_blockId(Parameter.value(`blockId`))) as? (String) -> Void + perform?(`blockId`) + var __value: DownloadDataTask? = nil + do { + __value = try methodReturnValue(.m_downloadDataTask__for_blockId(Parameter.value(`blockId`))).casted() + } catch { + // do nothing + } + return __value + } + + open func getDownloadDataTasks() -> [DownloadDataTask] { + addInvocation(.m_getDownloadDataTasks) + let perform = methodPerformValue(.m_getDownloadDataTasks) as? () -> Void + perform?() + var __value: [DownloadDataTask] + do { + __value = try methodReturnValue(.m_getDownloadDataTasks).casted() + } catch { + onFatalFailure("Stub return value not specified for getDownloadDataTasks(). Use given") + Failure("Stub return value not specified for getDownloadDataTasks(). Use given") + } + return __value + } + + open func getDownloadDataTasksForCourse(_ courseId: String) -> [DownloadDataTask] { + addInvocation(.m_getDownloadDataTasksForCourse__courseId(Parameter.value(`courseId`))) + let perform = methodPerformValue(.m_getDownloadDataTasksForCourse__courseId(Parameter.value(`courseId`))) as? (String) -> Void + perform?(`courseId`) + var __value: [DownloadDataTask] + do { + __value = try methodReturnValue(.m_getDownloadDataTasksForCourse__courseId(Parameter.value(`courseId`))).casted() + } catch { + onFatalFailure("Stub return value not specified for getDownloadDataTasksForCourse(_ courseId: String). Use given") + Failure("Stub return value not specified for getDownloadDataTasksForCourse(_ courseId: String). Use given") + } + return __value + } + + open func deleteDownloadDataTasks(ids: [String]) { + addInvocation(.m_deleteDownloadDataTasks__ids_ids(Parameter<[String]>.value(`ids`))) + let perform = methodPerformValue(.m_deleteDownloadDataTasks__ids_ids(Parameter<[String]>.value(`ids`))) as? ([String]) -> Void + perform?(`ids`) + } + + + fileprivate enum MethodType { + case m_set__userId_userId(Parameter) + case m_getUserID + case m_publisher + case m_addToDownloadQueue__tasks_tasks(Parameter<[DownloadDataTask]>) + case m_saveOfflineProgress__progress_progress(Parameter) + case m_loadProgress__for_blockID(Parameter) + case m_loadAllOfflineProgress + case m_deleteProgress__for_blockID(Parameter) + case m_deleteAllProgress + case m_addToDownloadQueue__blocks_blocksdownloadQuality_downloadQuality(Parameter<[CourseBlock]>, Parameter) + case m_updateTask__task_task(Parameter) + case m_downloadDataTask__for_blockId(Parameter) + case m_getDownloadDataTasks + case m_getDownloadDataTasksForCourse__courseId(Parameter) + case m_deleteDownloadDataTasks__ids_ids(Parameter<[String]>) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_set__userId_userId(let lhsUserid), .m_set__userId_userId(let rhsUserid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsUserid, rhs: rhsUserid, with: matcher), lhsUserid, rhsUserid, "userId")) + return Matcher.ComparisonResult(results) + + case (.m_getUserID, .m_getUserID): return .match + + case (.m_publisher, .m_publisher): return .match + + case (.m_addToDownloadQueue__tasks_tasks(let lhsTasks), .m_addToDownloadQueue__tasks_tasks(let rhsTasks)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTasks, rhs: rhsTasks, with: matcher), lhsTasks, rhsTasks, "tasks")) + return Matcher.ComparisonResult(results) + + case (.m_saveOfflineProgress__progress_progress(let lhsProgress), .m_saveOfflineProgress__progress_progress(let rhsProgress)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsProgress, rhs: rhsProgress, with: matcher), lhsProgress, rhsProgress, "progress")) + return Matcher.ComparisonResult(results) + + case (.m_loadProgress__for_blockID(let lhsBlockid), .m_loadProgress__for_blockID(let rhsBlockid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBlockid, rhs: rhsBlockid, with: matcher), lhsBlockid, rhsBlockid, "for blockID")) + return Matcher.ComparisonResult(results) + + case (.m_loadAllOfflineProgress, .m_loadAllOfflineProgress): return .match + + case (.m_deleteProgress__for_blockID(let lhsBlockid), .m_deleteProgress__for_blockID(let rhsBlockid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBlockid, rhs: rhsBlockid, with: matcher), lhsBlockid, rhsBlockid, "for blockID")) + return Matcher.ComparisonResult(results) + + case (.m_deleteAllProgress, .m_deleteAllProgress): return .match + + case (.m_addToDownloadQueue__blocks_blocksdownloadQuality_downloadQuality(let lhsBlocks, let lhsDownloadquality), .m_addToDownloadQueue__blocks_blocksdownloadQuality_downloadQuality(let rhsBlocks, let rhsDownloadquality)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBlocks, rhs: rhsBlocks, with: matcher), lhsBlocks, rhsBlocks, "blocks")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsDownloadquality, rhs: rhsDownloadquality, with: matcher), lhsDownloadquality, rhsDownloadquality, "downloadQuality")) + return Matcher.ComparisonResult(results) + + case (.m_updateTask__task_task(let lhsTask), .m_updateTask__task_task(let rhsTask)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTask, rhs: rhsTask, with: matcher), lhsTask, rhsTask, "task")) + return Matcher.ComparisonResult(results) + + case (.m_downloadDataTask__for_blockId(let lhsBlockid), .m_downloadDataTask__for_blockId(let rhsBlockid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBlockid, rhs: rhsBlockid, with: matcher), lhsBlockid, rhsBlockid, "for blockId")) + return Matcher.ComparisonResult(results) + + case (.m_getDownloadDataTasks, .m_getDownloadDataTasks): return .match + + case (.m_getDownloadDataTasksForCourse__courseId(let lhsCourseid), .m_getDownloadDataTasksForCourse__courseId(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "_ courseId")) + return Matcher.ComparisonResult(results) + + case (.m_deleteDownloadDataTasks__ids_ids(let lhsIds), .m_deleteDownloadDataTasks__ids_ids(let rhsIds)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsIds, rhs: rhsIds, with: matcher), lhsIds, rhsIds, "ids")) + return Matcher.ComparisonResult(results) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_set__userId_userId(p0): return p0.intValue + case .m_getUserID: return 0 + case .m_publisher: return 0 + case let .m_addToDownloadQueue__tasks_tasks(p0): return p0.intValue + case let .m_saveOfflineProgress__progress_progress(p0): return p0.intValue + case let .m_loadProgress__for_blockID(p0): return p0.intValue + case .m_loadAllOfflineProgress: return 0 + case let .m_deleteProgress__for_blockID(p0): return p0.intValue + case .m_deleteAllProgress: return 0 + case let .m_addToDownloadQueue__blocks_blocksdownloadQuality_downloadQuality(p0, p1): return p0.intValue + p1.intValue + case let .m_updateTask__task_task(p0): return p0.intValue + case let .m_downloadDataTask__for_blockId(p0): return p0.intValue + case .m_getDownloadDataTasks: return 0 + case let .m_getDownloadDataTasksForCourse__courseId(p0): return p0.intValue + case let .m_deleteDownloadDataTasks__ids_ids(p0): return p0.intValue + } + } + func assertionName() -> String { + switch self { + case .m_set__userId_userId: return ".set(userId:)" + case .m_getUserID: return ".getUserID()" + case .m_publisher: return ".publisher()" + case .m_addToDownloadQueue__tasks_tasks: return ".addToDownloadQueue(tasks:)" + case .m_saveOfflineProgress__progress_progress: return ".saveOfflineProgress(progress:)" + case .m_loadProgress__for_blockID: return ".loadProgress(for:)" + case .m_loadAllOfflineProgress: return ".loadAllOfflineProgress()" + case .m_deleteProgress__for_blockID: return ".deleteProgress(for:)" + case .m_deleteAllProgress: return ".deleteAllProgress()" + case .m_addToDownloadQueue__blocks_blocksdownloadQuality_downloadQuality: return ".addToDownloadQueue(blocks:downloadQuality:)" + case .m_updateTask__task_task: return ".updateTask(task:)" + case .m_downloadDataTask__for_blockId: return ".downloadDataTask(for:)" + case .m_getDownloadDataTasks: return ".getDownloadDataTasks()" + case .m_getDownloadDataTasksForCourse__courseId: return ".getDownloadDataTasksForCourse(_:)" + case .m_deleteDownloadDataTasks__ids_ids: return ".deleteDownloadDataTasks(ids:)" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + + public static func getUserID(willReturn: Int?...) -> MethodStub { + return Given(method: .m_getUserID, products: willReturn.map({ StubProduct.return($0 as Any) })) + } + @MainActor + public static func publisher(willReturn: AnyPublisher...) -> MethodStub { + return Given(method: .m_publisher, products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func loadProgress(for blockID: Parameter, willReturn: OfflineProgress?...) -> MethodStub { + return Given(method: .m_loadProgress__for_blockID(`blockID`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func loadAllOfflineProgress(willReturn: [OfflineProgress]...) -> MethodStub { + return Given(method: .m_loadAllOfflineProgress, products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func downloadDataTask(for blockId: Parameter, willReturn: DownloadDataTask?...) -> MethodStub { + return Given(method: .m_downloadDataTask__for_blockId(`blockId`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getDownloadDataTasks(willReturn: [DownloadDataTask]...) -> MethodStub { + return Given(method: .m_getDownloadDataTasks, products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getDownloadDataTasksForCourse(_ courseId: Parameter, willReturn: [DownloadDataTask]...) -> MethodStub { + return Given(method: .m_getDownloadDataTasksForCourse__courseId(`courseId`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getUserID(willProduce: (Stubber) -> Void) -> MethodStub { + let willReturn: [Int?] = [] + let given: Given = { return Given(method: .m_getUserID, products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (Int?).self) + willProduce(stubber) + return given + } + public static func loadProgress(for blockID: Parameter, willProduce: (Stubber) -> Void) -> MethodStub { + let willReturn: [OfflineProgress?] = [] + let given: Given = { return Given(method: .m_loadProgress__for_blockID(`blockID`), products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (OfflineProgress?).self) + willProduce(stubber) + return given + } + public static func loadAllOfflineProgress(willProduce: (Stubber<[OfflineProgress]>) -> Void) -> MethodStub { + let willReturn: [[OfflineProgress]] = [] + let given: Given = { return Given(method: .m_loadAllOfflineProgress, products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: ([OfflineProgress]).self) + willProduce(stubber) + return given + } + public static func downloadDataTask(for blockId: Parameter, willProduce: (Stubber) -> Void) -> MethodStub { + let willReturn: [DownloadDataTask?] = [] + let given: Given = { return Given(method: .m_downloadDataTask__for_blockId(`blockId`), products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (DownloadDataTask?).self) + willProduce(stubber) + return given + } + public static func getDownloadDataTasks(willProduce: (Stubber<[DownloadDataTask]>) -> Void) -> MethodStub { + let willReturn: [[DownloadDataTask]] = [] + let given: Given = { return Given(method: .m_getDownloadDataTasks, products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: ([DownloadDataTask]).self) + willProduce(stubber) + return given + } + public static func getDownloadDataTasksForCourse(_ courseId: Parameter, willProduce: (Stubber<[DownloadDataTask]>) -> Void) -> MethodStub { + let willReturn: [[DownloadDataTask]] = [] + let given: Given = { return Given(method: .m_getDownloadDataTasksForCourse__courseId(`courseId`), products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: ([DownloadDataTask]).self) + willProduce(stubber) + return given + } + @MainActor + public static func publisher(willThrow: Error...) -> MethodStub { + return Given(method: .m_publisher, products: willThrow.map({ StubProduct.throw($0) })) + } + @MainActor + public static func publisher(willProduce: (StubberThrows>) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_publisher, products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (AnyPublisher).self) + willProduce(stubber) + return given + } + } + + public struct Verify { + fileprivate var method: MethodType + + public static func set(userId: Parameter) -> Verify { return Verify(method: .m_set__userId_userId(`userId`))} + public static func getUserID() -> Verify { return Verify(method: .m_getUserID)} + @MainActor + public static func publisher() -> Verify { return Verify(method: .m_publisher)} + public static func addToDownloadQueue(tasks: Parameter<[DownloadDataTask]>) -> Verify { return Verify(method: .m_addToDownloadQueue__tasks_tasks(`tasks`))} + public static func saveOfflineProgress(progress: Parameter) -> Verify { return Verify(method: .m_saveOfflineProgress__progress_progress(`progress`))} + public static func loadProgress(for blockID: Parameter) -> Verify { return Verify(method: .m_loadProgress__for_blockID(`blockID`))} + public static func loadAllOfflineProgress() -> Verify { return Verify(method: .m_loadAllOfflineProgress)} + public static func deleteProgress(for blockID: Parameter) -> Verify { return Verify(method: .m_deleteProgress__for_blockID(`blockID`))} + public static func deleteAllProgress() -> Verify { return Verify(method: .m_deleteAllProgress)} + public static func addToDownloadQueue(blocks: Parameter<[CourseBlock]>, downloadQuality: Parameter) -> Verify { return Verify(method: .m_addToDownloadQueue__blocks_blocksdownloadQuality_downloadQuality(`blocks`, `downloadQuality`))} + public static func updateTask(task: Parameter) -> Verify { return Verify(method: .m_updateTask__task_task(`task`))} + public static func downloadDataTask(for blockId: Parameter) -> Verify { return Verify(method: .m_downloadDataTask__for_blockId(`blockId`))} + public static func getDownloadDataTasks() -> Verify { return Verify(method: .m_getDownloadDataTasks)} + public static func getDownloadDataTasksForCourse(_ courseId: Parameter) -> Verify { return Verify(method: .m_getDownloadDataTasksForCourse__courseId(`courseId`))} + public static func deleteDownloadDataTasks(ids: Parameter<[String]>) -> Verify { return Verify(method: .m_deleteDownloadDataTasks__ids_ids(`ids`))} + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func set(userId: Parameter, perform: @escaping (Int) -> Void) -> Perform { + return Perform(method: .m_set__userId_userId(`userId`), performs: perform) + } + public static func getUserID(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_getUserID, performs: perform) + } + @MainActor + public static func publisher(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_publisher, performs: perform) + } + public static func addToDownloadQueue(tasks: Parameter<[DownloadDataTask]>, perform: @escaping ([DownloadDataTask]) -> Void) -> Perform { + return Perform(method: .m_addToDownloadQueue__tasks_tasks(`tasks`), performs: perform) + } + public static func saveOfflineProgress(progress: Parameter, perform: @escaping (OfflineProgress) -> Void) -> Perform { + return Perform(method: .m_saveOfflineProgress__progress_progress(`progress`), performs: perform) + } + public static func loadProgress(for blockID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_loadProgress__for_blockID(`blockID`), performs: perform) + } + public static func loadAllOfflineProgress(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_loadAllOfflineProgress, performs: perform) + } + public static func deleteProgress(for blockID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_deleteProgress__for_blockID(`blockID`), performs: perform) + } + public static func deleteAllProgress(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_deleteAllProgress, performs: perform) + } + public static func addToDownloadQueue(blocks: Parameter<[CourseBlock]>, downloadQuality: Parameter, perform: @escaping ([CourseBlock], DownloadQuality) -> Void) -> Perform { + return Perform(method: .m_addToDownloadQueue__blocks_blocksdownloadQuality_downloadQuality(`blocks`, `downloadQuality`), performs: perform) + } + public static func updateTask(task: Parameter, perform: @escaping (DownloadDataTask) -> Void) -> Perform { + return Perform(method: .m_updateTask__task_task(`task`), performs: perform) + } + public static func downloadDataTask(for blockId: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_downloadDataTask__for_blockId(`blockId`), performs: perform) + } + public static func getDownloadDataTasks(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_getDownloadDataTasks, performs: perform) + } + public static func getDownloadDataTasksForCourse(_ courseId: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_getDownloadDataTasksForCourse__courseId(`courseId`), performs: perform) + } + public static func deleteDownloadDataTasks(ids: Parameter<[String]>, perform: @escaping ([String]) -> Void) -> Perform { + return Perform(method: .m_deleteDownloadDataTasks__ids_ids(`ids`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - CoreStorage + +open class CoreStorageMock: CoreStorage, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + public var accessToken: String? { + get { invocations.append(.p_accessToken_get); return __p_accessToken ?? optionalGivenGetterValue(.p_accessToken_get, "CoreStorageMock - stub value for accessToken was not defined") } + set { invocations.append(.p_accessToken_set(.value(newValue))); __p_accessToken = newValue } + } + private var __p_accessToken: (String)? + + public var refreshToken: String? { + get { invocations.append(.p_refreshToken_get); return __p_refreshToken ?? optionalGivenGetterValue(.p_refreshToken_get, "CoreStorageMock - stub value for refreshToken was not defined") } + set { invocations.append(.p_refreshToken_set(.value(newValue))); __p_refreshToken = newValue } + } + private var __p_refreshToken: (String)? + + public var pushToken: String? { + get { invocations.append(.p_pushToken_get); return __p_pushToken ?? optionalGivenGetterValue(.p_pushToken_get, "CoreStorageMock - stub value for pushToken was not defined") } + set { invocations.append(.p_pushToken_set(.value(newValue))); __p_pushToken = newValue } + } + private var __p_pushToken: (String)? + + public var appleSignFullName: String? { + get { invocations.append(.p_appleSignFullName_get); return __p_appleSignFullName ?? optionalGivenGetterValue(.p_appleSignFullName_get, "CoreStorageMock - stub value for appleSignFullName was not defined") } + set { invocations.append(.p_appleSignFullName_set(.value(newValue))); __p_appleSignFullName = newValue } + } + private var __p_appleSignFullName: (String)? + + public var appleSignEmail: String? { + get { invocations.append(.p_appleSignEmail_get); return __p_appleSignEmail ?? optionalGivenGetterValue(.p_appleSignEmail_get, "CoreStorageMock - stub value for appleSignEmail was not defined") } + set { invocations.append(.p_appleSignEmail_set(.value(newValue))); __p_appleSignEmail = newValue } + } + private var __p_appleSignEmail: (String)? + + public var cookiesDate: Date? { + get { invocations.append(.p_cookiesDate_get); return __p_cookiesDate ?? optionalGivenGetterValue(.p_cookiesDate_get, "CoreStorageMock - stub value for cookiesDate was not defined") } + set { invocations.append(.p_cookiesDate_set(.value(newValue))); __p_cookiesDate = newValue } + } + private var __p_cookiesDate: (Date)? + + public var reviewLastShownVersion: String? { + get { invocations.append(.p_reviewLastShownVersion_get); return __p_reviewLastShownVersion ?? optionalGivenGetterValue(.p_reviewLastShownVersion_get, "CoreStorageMock - stub value for reviewLastShownVersion was not defined") } + set { invocations.append(.p_reviewLastShownVersion_set(.value(newValue))); __p_reviewLastShownVersion = newValue } + } + private var __p_reviewLastShownVersion: (String)? + + public var lastReviewDate: Date? { + get { invocations.append(.p_lastReviewDate_get); return __p_lastReviewDate ?? optionalGivenGetterValue(.p_lastReviewDate_get, "CoreStorageMock - stub value for lastReviewDate was not defined") } + set { invocations.append(.p_lastReviewDate_set(.value(newValue))); __p_lastReviewDate = newValue } + } + private var __p_lastReviewDate: (Date)? + + public var user: DataLayer.User? { + get { invocations.append(.p_user_get); return __p_user ?? optionalGivenGetterValue(.p_user_get, "CoreStorageMock - stub value for user was not defined") } + set { invocations.append(.p_user_set(.value(newValue))); __p_user = newValue } + } + private var __p_user: (DataLayer.User)? + + public var userSettings: UserSettings? { + get { invocations.append(.p_userSettings_get); return __p_userSettings ?? optionalGivenGetterValue(.p_userSettings_get, "CoreStorageMock - stub value for userSettings was not defined") } + set { invocations.append(.p_userSettings_set(.value(newValue))); __p_userSettings = newValue } + } + private var __p_userSettings: (UserSettings)? + + public var resetAppSupportDirectoryUserData: Bool? { + get { invocations.append(.p_resetAppSupportDirectoryUserData_get); return __p_resetAppSupportDirectoryUserData ?? optionalGivenGetterValue(.p_resetAppSupportDirectoryUserData_get, "CoreStorageMock - stub value for resetAppSupportDirectoryUserData was not defined") } + set { invocations.append(.p_resetAppSupportDirectoryUserData_set(.value(newValue))); __p_resetAppSupportDirectoryUserData = newValue } + } + private var __p_resetAppSupportDirectoryUserData: (Bool)? + + public var useRelativeDates: Bool { + get { invocations.append(.p_useRelativeDates_get); return __p_useRelativeDates ?? givenGetterValue(.p_useRelativeDates_get, "CoreStorageMock - stub value for useRelativeDates was not defined") } + set { invocations.append(.p_useRelativeDates_set(.value(newValue))); __p_useRelativeDates = newValue } + } + private var __p_useRelativeDates: (Bool)? + + public var lastUsedSocialAuth: String? { + get { invocations.append(.p_lastUsedSocialAuth_get); return __p_lastUsedSocialAuth ?? optionalGivenGetterValue(.p_lastUsedSocialAuth_get, "CoreStorageMock - stub value for lastUsedSocialAuth was not defined") } + set { invocations.append(.p_lastUsedSocialAuth_set(.value(newValue))); __p_lastUsedSocialAuth = newValue } + } + private var __p_lastUsedSocialAuth: (String)? + + public var latestAvailableAppVersion: String? { + get { invocations.append(.p_latestAvailableAppVersion_get); return __p_latestAvailableAppVersion ?? optionalGivenGetterValue(.p_latestAvailableAppVersion_get, "CoreStorageMock - stub value for latestAvailableAppVersion was not defined") } + set { invocations.append(.p_latestAvailableAppVersion_set(.value(newValue))); __p_latestAvailableAppVersion = newValue } + } + private var __p_latestAvailableAppVersion: (String)? + + public var updateAppRequired: Bool { + get { invocations.append(.p_updateAppRequired_get); return __p_updateAppRequired ?? givenGetterValue(.p_updateAppRequired_get, "CoreStorageMock - stub value for updateAppRequired was not defined") } + set { invocations.append(.p_updateAppRequired_set(.value(newValue))); __p_updateAppRequired = newValue } + } + private var __p_updateAppRequired: (Bool)? + + + + + + open func clear() { + addInvocation(.m_clear) + let perform = methodPerformValue(.m_clear) as? () -> Void + perform?() + } + + + fileprivate enum MethodType { + case m_clear + case p_accessToken_get + case p_accessToken_set(Parameter) + case p_refreshToken_get + case p_refreshToken_set(Parameter) + case p_pushToken_get + case p_pushToken_set(Parameter) + case p_appleSignFullName_get + case p_appleSignFullName_set(Parameter) + case p_appleSignEmail_get + case p_appleSignEmail_set(Parameter) + case p_cookiesDate_get + case p_cookiesDate_set(Parameter) + case p_reviewLastShownVersion_get + case p_reviewLastShownVersion_set(Parameter) + case p_lastReviewDate_get + case p_lastReviewDate_set(Parameter) + case p_user_get + case p_user_set(Parameter) + case p_userSettings_get + case p_userSettings_set(Parameter) + case p_resetAppSupportDirectoryUserData_get + case p_resetAppSupportDirectoryUserData_set(Parameter) + case p_useRelativeDates_get + case p_useRelativeDates_set(Parameter) + case p_lastUsedSocialAuth_get + case p_lastUsedSocialAuth_set(Parameter) + case p_latestAvailableAppVersion_get + case p_latestAvailableAppVersion_set(Parameter) + case p_updateAppRequired_get + case p_updateAppRequired_set(Parameter) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_clear, .m_clear): return .match + case (.p_accessToken_get,.p_accessToken_get): return Matcher.ComparisonResult.match + case (.p_accessToken_set(let left),.p_accessToken_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_refreshToken_get,.p_refreshToken_get): return Matcher.ComparisonResult.match + case (.p_refreshToken_set(let left),.p_refreshToken_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_pushToken_get,.p_pushToken_get): return Matcher.ComparisonResult.match + case (.p_pushToken_set(let left),.p_pushToken_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_appleSignFullName_get,.p_appleSignFullName_get): return Matcher.ComparisonResult.match + case (.p_appleSignFullName_set(let left),.p_appleSignFullName_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_appleSignEmail_get,.p_appleSignEmail_get): return Matcher.ComparisonResult.match + case (.p_appleSignEmail_set(let left),.p_appleSignEmail_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_cookiesDate_get,.p_cookiesDate_get): return Matcher.ComparisonResult.match + case (.p_cookiesDate_set(let left),.p_cookiesDate_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_reviewLastShownVersion_get,.p_reviewLastShownVersion_get): return Matcher.ComparisonResult.match + case (.p_reviewLastShownVersion_set(let left),.p_reviewLastShownVersion_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_lastReviewDate_get,.p_lastReviewDate_get): return Matcher.ComparisonResult.match + case (.p_lastReviewDate_set(let left),.p_lastReviewDate_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_user_get,.p_user_get): return Matcher.ComparisonResult.match + case (.p_user_set(let left),.p_user_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_userSettings_get,.p_userSettings_get): return Matcher.ComparisonResult.match + case (.p_userSettings_set(let left),.p_userSettings_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_resetAppSupportDirectoryUserData_get,.p_resetAppSupportDirectoryUserData_get): return Matcher.ComparisonResult.match + case (.p_resetAppSupportDirectoryUserData_set(let left),.p_resetAppSupportDirectoryUserData_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_useRelativeDates_get,.p_useRelativeDates_get): return Matcher.ComparisonResult.match + case (.p_useRelativeDates_set(let left),.p_useRelativeDates_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_lastUsedSocialAuth_get,.p_lastUsedSocialAuth_get): return Matcher.ComparisonResult.match + case (.p_lastUsedSocialAuth_set(let left),.p_lastUsedSocialAuth_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_latestAvailableAppVersion_get,.p_latestAvailableAppVersion_get): return Matcher.ComparisonResult.match + case (.p_latestAvailableAppVersion_set(let left),.p_latestAvailableAppVersion_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_updateAppRequired_get,.p_updateAppRequired_get): return Matcher.ComparisonResult.match + case (.p_updateAppRequired_set(let left),.p_updateAppRequired_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case .m_clear: return 0 + case .p_accessToken_get: return 0 + case .p_accessToken_set(let newValue): return newValue.intValue + case .p_refreshToken_get: return 0 + case .p_refreshToken_set(let newValue): return newValue.intValue + case .p_pushToken_get: return 0 + case .p_pushToken_set(let newValue): return newValue.intValue + case .p_appleSignFullName_get: return 0 + case .p_appleSignFullName_set(let newValue): return newValue.intValue + case .p_appleSignEmail_get: return 0 + case .p_appleSignEmail_set(let newValue): return newValue.intValue + case .p_cookiesDate_get: return 0 + case .p_cookiesDate_set(let newValue): return newValue.intValue + case .p_reviewLastShownVersion_get: return 0 + case .p_reviewLastShownVersion_set(let newValue): return newValue.intValue + case .p_lastReviewDate_get: return 0 + case .p_lastReviewDate_set(let newValue): return newValue.intValue + case .p_user_get: return 0 + case .p_user_set(let newValue): return newValue.intValue + case .p_userSettings_get: return 0 + case .p_userSettings_set(let newValue): return newValue.intValue + case .p_resetAppSupportDirectoryUserData_get: return 0 + case .p_resetAppSupportDirectoryUserData_set(let newValue): return newValue.intValue + case .p_useRelativeDates_get: return 0 + case .p_useRelativeDates_set(let newValue): return newValue.intValue + case .p_lastUsedSocialAuth_get: return 0 + case .p_lastUsedSocialAuth_set(let newValue): return newValue.intValue + case .p_latestAvailableAppVersion_get: return 0 + case .p_latestAvailableAppVersion_set(let newValue): return newValue.intValue + case .p_updateAppRequired_get: return 0 + case .p_updateAppRequired_set(let newValue): return newValue.intValue + } + } + func assertionName() -> String { + switch self { + case .m_clear: return ".clear()" + case .p_accessToken_get: return "[get] .accessToken" + case .p_accessToken_set: return "[set] .accessToken" + case .p_refreshToken_get: return "[get] .refreshToken" + case .p_refreshToken_set: return "[set] .refreshToken" + case .p_pushToken_get: return "[get] .pushToken" + case .p_pushToken_set: return "[set] .pushToken" + case .p_appleSignFullName_get: return "[get] .appleSignFullName" + case .p_appleSignFullName_set: return "[set] .appleSignFullName" + case .p_appleSignEmail_get: return "[get] .appleSignEmail" + case .p_appleSignEmail_set: return "[set] .appleSignEmail" + case .p_cookiesDate_get: return "[get] .cookiesDate" + case .p_cookiesDate_set: return "[set] .cookiesDate" + case .p_reviewLastShownVersion_get: return "[get] .reviewLastShownVersion" + case .p_reviewLastShownVersion_set: return "[set] .reviewLastShownVersion" + case .p_lastReviewDate_get: return "[get] .lastReviewDate" + case .p_lastReviewDate_set: return "[set] .lastReviewDate" + case .p_user_get: return "[get] .user" + case .p_user_set: return "[set] .user" + case .p_userSettings_get: return "[get] .userSettings" + case .p_userSettings_set: return "[set] .userSettings" + case .p_resetAppSupportDirectoryUserData_get: return "[get] .resetAppSupportDirectoryUserData" + case .p_resetAppSupportDirectoryUserData_set: return "[set] .resetAppSupportDirectoryUserData" + case .p_useRelativeDates_get: return "[get] .useRelativeDates" + case .p_useRelativeDates_set: return "[set] .useRelativeDates" + case .p_lastUsedSocialAuth_get: return "[get] .lastUsedSocialAuth" + case .p_lastUsedSocialAuth_set: return "[set] .lastUsedSocialAuth" + case .p_latestAvailableAppVersion_get: return "[get] .latestAvailableAppVersion" + case .p_latestAvailableAppVersion_set: return "[set] .latestAvailableAppVersion" + case .p_updateAppRequired_get: return "[get] .updateAppRequired" + case .p_updateAppRequired_set: return "[set] .updateAppRequired" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + public static func accessToken(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_accessToken_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func refreshToken(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_refreshToken_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func pushToken(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_pushToken_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func appleSignFullName(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_appleSignFullName_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func appleSignEmail(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_appleSignEmail_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func cookiesDate(getter defaultValue: Date?...) -> PropertyStub { + return Given(method: .p_cookiesDate_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func reviewLastShownVersion(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_reviewLastShownVersion_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func lastReviewDate(getter defaultValue: Date?...) -> PropertyStub { + return Given(method: .p_lastReviewDate_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func user(getter defaultValue: DataLayer.User?...) -> PropertyStub { + return Given(method: .p_user_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func userSettings(getter defaultValue: UserSettings?...) -> PropertyStub { + return Given(method: .p_userSettings_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func resetAppSupportDirectoryUserData(getter defaultValue: Bool?...) -> PropertyStub { + return Given(method: .p_resetAppSupportDirectoryUserData_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func useRelativeDates(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_useRelativeDates_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func lastUsedSocialAuth(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_lastUsedSocialAuth_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func latestAvailableAppVersion(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_latestAvailableAppVersion_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func updateAppRequired(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_updateAppRequired_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + + } + + public struct Verify { + fileprivate var method: MethodType + + public static func clear() -> Verify { return Verify(method: .m_clear)} + public static var accessToken: Verify { return Verify(method: .p_accessToken_get) } + public static func accessToken(set newValue: Parameter) -> Verify { return Verify(method: .p_accessToken_set(newValue)) } + public static var refreshToken: Verify { return Verify(method: .p_refreshToken_get) } + public static func refreshToken(set newValue: Parameter) -> Verify { return Verify(method: .p_refreshToken_set(newValue)) } + public static var pushToken: Verify { return Verify(method: .p_pushToken_get) } + public static func pushToken(set newValue: Parameter) -> Verify { return Verify(method: .p_pushToken_set(newValue)) } + public static var appleSignFullName: Verify { return Verify(method: .p_appleSignFullName_get) } + public static func appleSignFullName(set newValue: Parameter) -> Verify { return Verify(method: .p_appleSignFullName_set(newValue)) } + public static var appleSignEmail: Verify { return Verify(method: .p_appleSignEmail_get) } + public static func appleSignEmail(set newValue: Parameter) -> Verify { return Verify(method: .p_appleSignEmail_set(newValue)) } + public static var cookiesDate: Verify { return Verify(method: .p_cookiesDate_get) } + public static func cookiesDate(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesDate_set(newValue)) } + public static var reviewLastShownVersion: Verify { return Verify(method: .p_reviewLastShownVersion_get) } + public static func reviewLastShownVersion(set newValue: Parameter) -> Verify { return Verify(method: .p_reviewLastShownVersion_set(newValue)) } + public static var lastReviewDate: Verify { return Verify(method: .p_lastReviewDate_get) } + public static func lastReviewDate(set newValue: Parameter) -> Verify { return Verify(method: .p_lastReviewDate_set(newValue)) } + public static var user: Verify { return Verify(method: .p_user_get) } + public static func user(set newValue: Parameter) -> Verify { return Verify(method: .p_user_set(newValue)) } + public static var userSettings: Verify { return Verify(method: .p_userSettings_get) } + public static func userSettings(set newValue: Parameter) -> Verify { return Verify(method: .p_userSettings_set(newValue)) } + public static var resetAppSupportDirectoryUserData: Verify { return Verify(method: .p_resetAppSupportDirectoryUserData_get) } + public static func resetAppSupportDirectoryUserData(set newValue: Parameter) -> Verify { return Verify(method: .p_resetAppSupportDirectoryUserData_set(newValue)) } + public static var useRelativeDates: Verify { return Verify(method: .p_useRelativeDates_get) } + public static func useRelativeDates(set newValue: Parameter) -> Verify { return Verify(method: .p_useRelativeDates_set(newValue)) } + public static var lastUsedSocialAuth: Verify { return Verify(method: .p_lastUsedSocialAuth_get) } + public static func lastUsedSocialAuth(set newValue: Parameter) -> Verify { return Verify(method: .p_lastUsedSocialAuth_set(newValue)) } + public static var latestAvailableAppVersion: Verify { return Verify(method: .p_latestAvailableAppVersion_get) } + public static func latestAvailableAppVersion(set newValue: Parameter) -> Verify { return Verify(method: .p_latestAvailableAppVersion_set(newValue)) } + public static var updateAppRequired: Verify { return Verify(method: .p_updateAppRequired_get) } + public static func updateAppRequired(set newValue: Parameter) -> Verify { return Verify(method: .p_updateAppRequired_set(newValue)) } + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func clear(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_clear, performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - CourseStructureManagerProtocol + +open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + + + + + open func getCourseBlocks(courseID: String) throws -> CourseStructure { + addInvocation(.m_getCourseBlocks__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_getCourseBlocks__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + var __value: CourseStructure + do { + __value = try methodReturnValue(.m_getCourseBlocks__courseID_courseID(Parameter.value(`courseID`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for getCourseBlocks(courseID: String). Use given") + Failure("Stub return value not specified for getCourseBlocks(courseID: String). Use given") + } catch { + throw error + } + return __value + } + + open func getLoadedCourseBlocks(courseID: String) throws -> CourseStructure { + addInvocation(.m_getLoadedCourseBlocks__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_getLoadedCourseBlocks__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + var __value: CourseStructure + do { + __value = try methodReturnValue(.m_getLoadedCourseBlocks__courseID_courseID(Parameter.value(`courseID`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for getLoadedCourseBlocks(courseID: String). Use given") + Failure("Stub return value not specified for getLoadedCourseBlocks(courseID: String). Use given") + } catch { + throw error + } + return __value + } + + open func shiftDueDates(courseID: String) throws { + addInvocation(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + do { + _ = try methodReturnValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + + + fileprivate enum MethodType { + case m_getCourseBlocks__courseID_courseID(Parameter) + case m_getLoadedCourseBlocks__courseID_courseID(Parameter) + case m_shiftDueDates__courseID_courseID(Parameter) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_getCourseBlocks__courseID_courseID(let lhsCourseid), .m_getCourseBlocks__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) + + case (.m_getLoadedCourseBlocks__courseID_courseID(let lhsCourseid), .m_getLoadedCourseBlocks__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) + + case (.m_shiftDueDates__courseID_courseID(let lhsCourseid), .m_shiftDueDates__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_getCourseBlocks__courseID_courseID(p0): return p0.intValue + case let .m_getLoadedCourseBlocks__courseID_courseID(p0): return p0.intValue + case let .m_shiftDueDates__courseID_courseID(p0): return p0.intValue + } + } + func assertionName() -> String { + switch self { + case .m_getCourseBlocks__courseID_courseID: return ".getCourseBlocks(courseID:)" + case .m_getLoadedCourseBlocks__courseID_courseID: return ".getLoadedCourseBlocks(courseID:)" + case .m_shiftDueDates__courseID_courseID: return ".shiftDueDates(courseID:)" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + + public static func getCourseBlocks(courseID: Parameter, willReturn: CourseStructure...) -> MethodStub { + return Given(method: .m_getCourseBlocks__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getLoadedCourseBlocks(courseID: Parameter, willReturn: CourseStructure...) -> MethodStub { + return Given(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getCourseBlocks(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getCourseBlocks__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func getCourseBlocks(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_getCourseBlocks__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (CourseStructure).self) + willProduce(stubber) + return given + } + public static func getLoadedCourseBlocks(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func getLoadedCourseBlocks(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (CourseStructure).self) + willProduce(stubber) + return given + } + public static func shiftDueDates(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func shiftDueDates(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } + } + + public struct Verify { + fileprivate var method: MethodType + + public static func getCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocks__courseID_courseID(`courseID`))} + public static func getLoadedCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`))} + public static func shiftDueDates(courseID: Parameter) -> Verify { return Verify(method: .m_shiftDueDates__courseID_courseID(`courseID`))} + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func getCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_getCourseBlocks__courseID_courseID(`courseID`), performs: perform) + } + public static func getLoadedCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), performs: perform) + } + public static func shiftDueDates(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_shiftDueDates__courseID_courseID(`courseID`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - DatesInteractorProtocol + +open class DatesInteractorProtocolMock: DatesInteractorProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + + + + + open func getCourseDates(page: Int) throws -> ([CourseDate], String?) { + addInvocation(.m_getCourseDates__page_page(Parameter.value(`page`))) + let perform = methodPerformValue(.m_getCourseDates__page_page(Parameter.value(`page`))) as? (Int) -> Void + perform?(`page`) + var __value: ([CourseDate], String?) + do { + __value = try methodReturnValue(.m_getCourseDates__page_page(Parameter.value(`page`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for getCourseDates(page: Int). Use given") + Failure("Stub return value not specified for getCourseDates(page: Int). Use given") + } catch { + throw error + } + return __value + } + + open func getCourseDatesOffline(limit: Int?, offset: Int?) throws -> [CourseDate] { + addInvocation(.m_getCourseDatesOffline__limit_limitoffset_offset(Parameter.value(`limit`), Parameter.value(`offset`))) + let perform = methodPerformValue(.m_getCourseDatesOffline__limit_limitoffset_offset(Parameter.value(`limit`), Parameter.value(`offset`))) as? (Int?, Int?) -> Void + perform?(`limit`, `offset`) + var __value: [CourseDate] + do { + __value = try methodReturnValue(.m_getCourseDatesOffline__limit_limitoffset_offset(Parameter.value(`limit`), Parameter.value(`offset`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for getCourseDatesOffline(limit: Int?, offset: Int?). Use given") + Failure("Stub return value not specified for getCourseDatesOffline(limit: Int?, offset: Int?). Use given") + } catch { + throw error + } + return __value + } + + open func resetAllRelativeCourseDeadlines() throws { + addInvocation(.m_resetAllRelativeCourseDeadlines) + let perform = methodPerformValue(.m_resetAllRelativeCourseDeadlines) as? () -> Void + perform?() + do { + _ = try methodReturnValue(.m_resetAllRelativeCourseDeadlines).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + + + fileprivate enum MethodType { + case m_getCourseDates__page_page(Parameter) + case m_getCourseDatesOffline__limit_limitoffset_offset(Parameter, Parameter) + case m_resetAllRelativeCourseDeadlines + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_getCourseDates__page_page(let lhsPage), .m_getCourseDates__page_page(let rhsPage)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPage, rhs: rhsPage, with: matcher), lhsPage, rhsPage, "page")) + return Matcher.ComparisonResult(results) + + case (.m_getCourseDatesOffline__limit_limitoffset_offset(let lhsLimit, let lhsOffset), .m_getCourseDatesOffline__limit_limitoffset_offset(let rhsLimit, let rhsOffset)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsLimit, rhs: rhsLimit, with: matcher), lhsLimit, rhsLimit, "limit")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsOffset, rhs: rhsOffset, with: matcher), lhsOffset, rhsOffset, "offset")) + return Matcher.ComparisonResult(results) + + case (.m_resetAllRelativeCourseDeadlines, .m_resetAllRelativeCourseDeadlines): return .match + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_getCourseDates__page_page(p0): return p0.intValue + case let .m_getCourseDatesOffline__limit_limitoffset_offset(p0, p1): return p0.intValue + p1.intValue + case .m_resetAllRelativeCourseDeadlines: return 0 + } + } + func assertionName() -> String { + switch self { + case .m_getCourseDates__page_page: return ".getCourseDates(page:)" + case .m_getCourseDatesOffline__limit_limitoffset_offset: return ".getCourseDatesOffline(limit:offset:)" + case .m_resetAllRelativeCourseDeadlines: return ".resetAllRelativeCourseDeadlines()" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + + public static func getCourseDates(page: Parameter, willReturn: ([CourseDate], String?)...) -> MethodStub { + return Given(method: .m_getCourseDates__page_page(`page`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getCourseDatesOffline(limit: Parameter, offset: Parameter, willReturn: [CourseDate]...) -> MethodStub { + return Given(method: .m_getCourseDatesOffline__limit_limitoffset_offset(`limit`, `offset`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getCourseDates(page: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getCourseDates__page_page(`page`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func getCourseDates(page: Parameter, willProduce: (StubberThrows<([CourseDate], String?)>) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_getCourseDates__page_page(`page`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (([CourseDate], String?)).self) + willProduce(stubber) + return given + } + public static func getCourseDatesOffline(limit: Parameter, offset: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getCourseDatesOffline__limit_limitoffset_offset(`limit`, `offset`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func getCourseDatesOffline(limit: Parameter, offset: Parameter, willProduce: (StubberThrows<[CourseDate]>) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_getCourseDatesOffline__limit_limitoffset_offset(`limit`, `offset`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: ([CourseDate]).self) + willProduce(stubber) + return given + } + public static func resetAllRelativeCourseDeadlines(willThrow: Error...) -> MethodStub { + return Given(method: .m_resetAllRelativeCourseDeadlines, products: willThrow.map({ StubProduct.throw($0) })) + } + public static func resetAllRelativeCourseDeadlines(willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_resetAllRelativeCourseDeadlines, products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } + } + + public struct Verify { + fileprivate var method: MethodType + + public static func getCourseDates(page: Parameter) -> Verify { return Verify(method: .m_getCourseDates__page_page(`page`))} + public static func getCourseDatesOffline(limit: Parameter, offset: Parameter) -> Verify { return Verify(method: .m_getCourseDatesOffline__limit_limitoffset_offset(`limit`, `offset`))} + public static func resetAllRelativeCourseDeadlines() -> Verify { return Verify(method: .m_resetAllRelativeCourseDeadlines)} + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func getCourseDates(page: Parameter, perform: @escaping (Int) -> Void) -> Perform { + return Perform(method: .m_getCourseDates__page_page(`page`), performs: perform) + } + public static func getCourseDatesOffline(limit: Parameter, offset: Parameter, perform: @escaping (Int?, Int?) -> Void) -> Perform { + return Perform(method: .m_getCourseDatesOffline__limit_limitoffset_offset(`limit`, `offset`), performs: perform) + } + public static func resetAllRelativeCourseDeadlines(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_resetAllRelativeCourseDeadlines, performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - DatesPersistenceProtocol + +open class DatesPersistenceProtocolMock: DatesPersistenceProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + + + + + open func loadCourseDates(limit: Int?, offset: Int?) throws -> [CourseDate] { + addInvocation(.m_loadCourseDates__limit_limitoffset_offset(Parameter.value(`limit`), Parameter.value(`offset`))) + let perform = methodPerformValue(.m_loadCourseDates__limit_limitoffset_offset(Parameter.value(`limit`), Parameter.value(`offset`))) as? (Int?, Int?) -> Void + perform?(`limit`, `offset`) + var __value: [CourseDate] + do { + __value = try methodReturnValue(.m_loadCourseDates__limit_limitoffset_offset(Parameter.value(`limit`), Parameter.value(`offset`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for loadCourseDates(limit: Int?, offset: Int?). Use given") + Failure("Stub return value not specified for loadCourseDates(limit: Int?, offset: Int?). Use given") + } catch { + throw error + } + return __value + } + + open func saveCourseDates(dates: [CourseDate], startIndex: Int) { + addInvocation(.m_saveCourseDates__dates_datesstartIndex_startIndex(Parameter<[CourseDate]>.value(`dates`), Parameter.value(`startIndex`))) + let perform = methodPerformValue(.m_saveCourseDates__dates_datesstartIndex_startIndex(Parameter<[CourseDate]>.value(`dates`), Parameter.value(`startIndex`))) as? ([CourseDate], Int) -> Void + perform?(`dates`, `startIndex`) + } + + open func clearAllCourseDates() { + addInvocation(.m_clearAllCourseDates) + let perform = methodPerformValue(.m_clearAllCourseDates) as? () -> Void + perform?() + } + + + fileprivate enum MethodType { + case m_loadCourseDates__limit_limitoffset_offset(Parameter, Parameter) + case m_saveCourseDates__dates_datesstartIndex_startIndex(Parameter<[CourseDate]>, Parameter) + case m_clearAllCourseDates + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_loadCourseDates__limit_limitoffset_offset(let lhsLimit, let lhsOffset), .m_loadCourseDates__limit_limitoffset_offset(let rhsLimit, let rhsOffset)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsLimit, rhs: rhsLimit, with: matcher), lhsLimit, rhsLimit, "limit")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsOffset, rhs: rhsOffset, with: matcher), lhsOffset, rhsOffset, "offset")) + return Matcher.ComparisonResult(results) + + case (.m_saveCourseDates__dates_datesstartIndex_startIndex(let lhsDates, let lhsStartindex), .m_saveCourseDates__dates_datesstartIndex_startIndex(let rhsDates, let rhsStartindex)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsDates, rhs: rhsDates, with: matcher), lhsDates, rhsDates, "dates")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsStartindex, rhs: rhsStartindex, with: matcher), lhsStartindex, rhsStartindex, "startIndex")) + return Matcher.ComparisonResult(results) + + case (.m_clearAllCourseDates, .m_clearAllCourseDates): return .match + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_loadCourseDates__limit_limitoffset_offset(p0, p1): return p0.intValue + p1.intValue + case let .m_saveCourseDates__dates_datesstartIndex_startIndex(p0, p1): return p0.intValue + p1.intValue + case .m_clearAllCourseDates: return 0 + } + } + func assertionName() -> String { + switch self { + case .m_loadCourseDates__limit_limitoffset_offset: return ".loadCourseDates(limit:offset:)" + case .m_saveCourseDates__dates_datesstartIndex_startIndex: return ".saveCourseDates(dates:startIndex:)" + case .m_clearAllCourseDates: return ".clearAllCourseDates()" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + + public static func loadCourseDates(limit: Parameter, offset: Parameter, willReturn: [CourseDate]...) -> MethodStub { + return Given(method: .m_loadCourseDates__limit_limitoffset_offset(`limit`, `offset`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func loadCourseDates(limit: Parameter, offset: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_loadCourseDates__limit_limitoffset_offset(`limit`, `offset`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func loadCourseDates(limit: Parameter, offset: Parameter, willProduce: (StubberThrows<[CourseDate]>) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_loadCourseDates__limit_limitoffset_offset(`limit`, `offset`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: ([CourseDate]).self) + willProduce(stubber) + return given + } + } + + public struct Verify { + fileprivate var method: MethodType + + public static func loadCourseDates(limit: Parameter, offset: Parameter) -> Verify { return Verify(method: .m_loadCourseDates__limit_limitoffset_offset(`limit`, `offset`))} + public static func saveCourseDates(dates: Parameter<[CourseDate]>, startIndex: Parameter) -> Verify { return Verify(method: .m_saveCourseDates__dates_datesstartIndex_startIndex(`dates`, `startIndex`))} + public static func clearAllCourseDates() -> Verify { return Verify(method: .m_clearAllCourseDates)} + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func loadCourseDates(limit: Parameter, offset: Parameter, perform: @escaping (Int?, Int?) -> Void) -> Perform { + return Perform(method: .m_loadCourseDates__limit_limitoffset_offset(`limit`, `offset`), performs: perform) + } + public static func saveCourseDates(dates: Parameter<[CourseDate]>, startIndex: Parameter, perform: @escaping ([CourseDate], Int) -> Void) -> Perform { + return Perform(method: .m_saveCourseDates__dates_datesstartIndex_startIndex(`dates`, `startIndex`), performs: perform) + } + public static func clearAllCourseDates(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_clearAllCourseDates, performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - DownloadManagerProtocol + +open class DownloadManagerProtocolMock: DownloadManagerProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + + + + + open func getCurrentDownloadTask() -> DownloadDataTask? { + addInvocation(.m_getCurrentDownloadTask) + let perform = methodPerformValue(.m_getCurrentDownloadTask) as? () -> Void + perform?() + var __value: DownloadDataTask? = nil + do { + __value = try methodReturnValue(.m_getCurrentDownloadTask).casted() + } catch { + // do nothing + } + return __value + } + + open func eventPublisher() -> AnyPublisher { + addInvocation(.m_eventPublisher) + let perform = methodPerformValue(.m_eventPublisher) as? () -> Void + perform?() + var __value: AnyPublisher + do { + __value = try methodReturnValue(.m_eventPublisher).casted() + } catch { + onFatalFailure("Stub return value not specified for eventPublisher(). Use given") + Failure("Stub return value not specified for eventPublisher(). Use given") + } + return __value + } + + open func addToDownloadQueue(blocks: [CourseBlock]) throws { + addInvocation(.m_addToDownloadQueue__blocks_blocks(Parameter<[CourseBlock]>.value(`blocks`))) + let perform = methodPerformValue(.m_addToDownloadQueue__blocks_blocks(Parameter<[CourseBlock]>.value(`blocks`))) as? ([CourseBlock]) -> Void + perform?(`blocks`) + do { + _ = try methodReturnValue(.m_addToDownloadQueue__blocks_blocks(Parameter<[CourseBlock]>.value(`blocks`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + + open func getDownloadTasks() -> [DownloadDataTask] { + addInvocation(.m_getDownloadTasks) + let perform = methodPerformValue(.m_getDownloadTasks) as? () -> Void + perform?() + var __value: [DownloadDataTask] + do { + __value = try methodReturnValue(.m_getDownloadTasks).casted() + } catch { + onFatalFailure("Stub return value not specified for getDownloadTasks(). Use given") + Failure("Stub return value not specified for getDownloadTasks(). Use given") + } + return __value + } + + open func getDownloadTasksForCourse(_ courseId: String) -> [DownloadDataTask] { + addInvocation(.m_getDownloadTasksForCourse__courseId(Parameter.value(`courseId`))) + let perform = methodPerformValue(.m_getDownloadTasksForCourse__courseId(Parameter.value(`courseId`))) as? (String) -> Void + perform?(`courseId`) + var __value: [DownloadDataTask] + do { + __value = try methodReturnValue(.m_getDownloadTasksForCourse__courseId(Parameter.value(`courseId`))).casted() + } catch { + onFatalFailure("Stub return value not specified for getDownloadTasksForCourse(_ courseId: String). Use given") + Failure("Stub return value not specified for getDownloadTasksForCourse(_ courseId: String). Use given") + } + return __value + } + + open func cancelDownloading(courseId: String, blocks: [CourseBlock]) throws { + addInvocation(.m_cancelDownloading__courseId_courseIdblocks_blocks(Parameter.value(`courseId`), Parameter<[CourseBlock]>.value(`blocks`))) + let perform = methodPerformValue(.m_cancelDownloading__courseId_courseIdblocks_blocks(Parameter.value(`courseId`), Parameter<[CourseBlock]>.value(`blocks`))) as? (String, [CourseBlock]) -> Void + perform?(`courseId`, `blocks`) + do { + _ = try methodReturnValue(.m_cancelDownloading__courseId_courseIdblocks_blocks(Parameter.value(`courseId`), Parameter<[CourseBlock]>.value(`blocks`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + + open func cancelDownloading(task: DownloadDataTask) throws { + addInvocation(.m_cancelDownloading__task_task(Parameter.value(`task`))) + let perform = methodPerformValue(.m_cancelDownloading__task_task(Parameter.value(`task`))) as? (DownloadDataTask) -> Void + perform?(`task`) + do { + _ = try methodReturnValue(.m_cancelDownloading__task_task(Parameter.value(`task`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + + open func cancelDownloading(courseId: String) throws { + addInvocation(.m_cancelDownloading__courseId_courseId(Parameter.value(`courseId`))) + let perform = methodPerformValue(.m_cancelDownloading__courseId_courseId(Parameter.value(`courseId`))) as? (String) -> Void + perform?(`courseId`) + do { + _ = try methodReturnValue(.m_cancelDownloading__courseId_courseId(Parameter.value(`courseId`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + + open func cancelAllDownloading() throws { + addInvocation(.m_cancelAllDownloading) + let perform = methodPerformValue(.m_cancelAllDownloading) as? () -> Void + perform?() + do { + _ = try methodReturnValue(.m_cancelAllDownloading).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + + open func deleteAll() { + addInvocation(.m_deleteAll) + let perform = methodPerformValue(.m_deleteAll) as? () -> Void + perform?() + } + + open func fileUrl(for blockId: String) -> URL? { + addInvocation(.m_fileUrl__for_blockId(Parameter.value(`blockId`))) + let perform = methodPerformValue(.m_fileUrl__for_blockId(Parameter.value(`blockId`))) as? (String) -> Void + perform?(`blockId`) + var __value: URL? = nil + do { + __value = try methodReturnValue(.m_fileUrl__for_blockId(Parameter.value(`blockId`))).casted() + } catch { + // do nothing + } + return __value + } + + open func resumeDownloading() throws { + addInvocation(.m_resumeDownloading) + let perform = methodPerformValue(.m_resumeDownloading) as? () -> Void + perform?() + do { + _ = try methodReturnValue(.m_resumeDownloading).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + + open func isLargeVideosSize(blocks: [CourseBlock]) -> Bool { + addInvocation(.m_isLargeVideosSize__blocks_blocks(Parameter<[CourseBlock]>.value(`blocks`))) + let perform = methodPerformValue(.m_isLargeVideosSize__blocks_blocks(Parameter<[CourseBlock]>.value(`blocks`))) as? ([CourseBlock]) -> Void + perform?(`blocks`) + var __value: Bool + do { + __value = try methodReturnValue(.m_isLargeVideosSize__blocks_blocks(Parameter<[CourseBlock]>.value(`blocks`))).casted() + } catch { + onFatalFailure("Stub return value not specified for isLargeVideosSize(blocks: [CourseBlock]). Use given") + Failure("Stub return value not specified for isLargeVideosSize(blocks: [CourseBlock]). Use given") + } + return __value + } + + open func removeAppSupportDirectoryUnusedContent() { + addInvocation(.m_removeAppSupportDirectoryUnusedContent) + let perform = methodPerformValue(.m_removeAppSupportDirectoryUnusedContent) as? () -> Void + perform?() + } + + open func delete(blocks: [CourseBlock], courseId: String) { + addInvocation(.m_delete__blocks_blockscourseId_courseId(Parameter<[CourseBlock]>.value(`blocks`), Parameter.value(`courseId`))) + let perform = methodPerformValue(.m_delete__blocks_blockscourseId_courseId(Parameter<[CourseBlock]>.value(`blocks`), Parameter.value(`courseId`))) as? ([CourseBlock], String) -> Void + perform?(`blocks`, `courseId`) + } + + open func downloadTask(for blockId: String) -> DownloadDataTask? { + addInvocation(.m_downloadTask__for_blockId(Parameter.value(`blockId`))) + let perform = methodPerformValue(.m_downloadTask__for_blockId(Parameter.value(`blockId`))) as? (String) -> Void + perform?(`blockId`) + var __value: DownloadDataTask? = nil + do { + __value = try methodReturnValue(.m_downloadTask__for_blockId(Parameter.value(`blockId`))).casted() + } catch { + // do nothing + } + return __value + } + + open func getFreeDiskSpace() -> Int? { + addInvocation(.m_getFreeDiskSpace) + let perform = methodPerformValue(.m_getFreeDiskSpace) as? () -> Void + perform?() + var __value: Int? = nil + do { + __value = try methodReturnValue(.m_getFreeDiskSpace).casted() + } catch { + // do nothing + } + return __value + } + + + fileprivate enum MethodType { + case m_getCurrentDownloadTask + case m_eventPublisher + case m_addToDownloadQueue__blocks_blocks(Parameter<[CourseBlock]>) + case m_getDownloadTasks + case m_getDownloadTasksForCourse__courseId(Parameter) + case m_cancelDownloading__courseId_courseIdblocks_blocks(Parameter, Parameter<[CourseBlock]>) + case m_cancelDownloading__task_task(Parameter) + case m_cancelDownloading__courseId_courseId(Parameter) + case m_cancelAllDownloading + case m_deleteAll + case m_fileUrl__for_blockId(Parameter) + case m_resumeDownloading + case m_isLargeVideosSize__blocks_blocks(Parameter<[CourseBlock]>) + case m_removeAppSupportDirectoryUnusedContent + case m_delete__blocks_blockscourseId_courseId(Parameter<[CourseBlock]>, Parameter) + case m_downloadTask__for_blockId(Parameter) + case m_getFreeDiskSpace + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_getCurrentDownloadTask, .m_getCurrentDownloadTask): return .match + + case (.m_eventPublisher, .m_eventPublisher): return .match + + case (.m_addToDownloadQueue__blocks_blocks(let lhsBlocks), .m_addToDownloadQueue__blocks_blocks(let rhsBlocks)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBlocks, rhs: rhsBlocks, with: matcher), lhsBlocks, rhsBlocks, "blocks")) + return Matcher.ComparisonResult(results) + + case (.m_getDownloadTasks, .m_getDownloadTasks): return .match + + case (.m_getDownloadTasksForCourse__courseId(let lhsCourseid), .m_getDownloadTasksForCourse__courseId(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "_ courseId")) + return Matcher.ComparisonResult(results) + + case (.m_cancelDownloading__courseId_courseIdblocks_blocks(let lhsCourseid, let lhsBlocks), .m_cancelDownloading__courseId_courseIdblocks_blocks(let rhsCourseid, let rhsBlocks)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseId")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBlocks, rhs: rhsBlocks, with: matcher), lhsBlocks, rhsBlocks, "blocks")) + return Matcher.ComparisonResult(results) + + case (.m_cancelDownloading__task_task(let lhsTask), .m_cancelDownloading__task_task(let rhsTask)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTask, rhs: rhsTask, with: matcher), lhsTask, rhsTask, "task")) + return Matcher.ComparisonResult(results) + + case (.m_cancelDownloading__courseId_courseId(let lhsCourseid), .m_cancelDownloading__courseId_courseId(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseId")) + return Matcher.ComparisonResult(results) + + case (.m_cancelAllDownloading, .m_cancelAllDownloading): return .match + + case (.m_deleteAll, .m_deleteAll): return .match + + case (.m_fileUrl__for_blockId(let lhsBlockid), .m_fileUrl__for_blockId(let rhsBlockid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBlockid, rhs: rhsBlockid, with: matcher), lhsBlockid, rhsBlockid, "for blockId")) + return Matcher.ComparisonResult(results) + + case (.m_resumeDownloading, .m_resumeDownloading): return .match + + case (.m_isLargeVideosSize__blocks_blocks(let lhsBlocks), .m_isLargeVideosSize__blocks_blocks(let rhsBlocks)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBlocks, rhs: rhsBlocks, with: matcher), lhsBlocks, rhsBlocks, "blocks")) + return Matcher.ComparisonResult(results) + + case (.m_removeAppSupportDirectoryUnusedContent, .m_removeAppSupportDirectoryUnusedContent): return .match + + case (.m_delete__blocks_blockscourseId_courseId(let lhsBlocks, let lhsCourseid), .m_delete__blocks_blockscourseId_courseId(let rhsBlocks, let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBlocks, rhs: rhsBlocks, with: matcher), lhsBlocks, rhsBlocks, "blocks")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseId")) + return Matcher.ComparisonResult(results) + + case (.m_downloadTask__for_blockId(let lhsBlockid), .m_downloadTask__for_blockId(let rhsBlockid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBlockid, rhs: rhsBlockid, with: matcher), lhsBlockid, rhsBlockid, "for blockId")) + return Matcher.ComparisonResult(results) + + case (.m_getFreeDiskSpace, .m_getFreeDiskSpace): return .match + default: return .none + } + } + + func intValue() -> Int { + switch self { + case .m_getCurrentDownloadTask: return 0 + case .m_eventPublisher: return 0 + case let .m_addToDownloadQueue__blocks_blocks(p0): return p0.intValue + case .m_getDownloadTasks: return 0 + case let .m_getDownloadTasksForCourse__courseId(p0): return p0.intValue + case let .m_cancelDownloading__courseId_courseIdblocks_blocks(p0, p1): return p0.intValue + p1.intValue + case let .m_cancelDownloading__task_task(p0): return p0.intValue + case let .m_cancelDownloading__courseId_courseId(p0): return p0.intValue + case .m_cancelAllDownloading: return 0 + case .m_deleteAll: return 0 + case let .m_fileUrl__for_blockId(p0): return p0.intValue + case .m_resumeDownloading: return 0 + case let .m_isLargeVideosSize__blocks_blocks(p0): return p0.intValue + case .m_removeAppSupportDirectoryUnusedContent: return 0 + case let .m_delete__blocks_blockscourseId_courseId(p0, p1): return p0.intValue + p1.intValue + case let .m_downloadTask__for_blockId(p0): return p0.intValue + case .m_getFreeDiskSpace: return 0 + } + } + func assertionName() -> String { + switch self { + case .m_getCurrentDownloadTask: return ".getCurrentDownloadTask()" + case .m_eventPublisher: return ".eventPublisher()" + case .m_addToDownloadQueue__blocks_blocks: return ".addToDownloadQueue(blocks:)" + case .m_getDownloadTasks: return ".getDownloadTasks()" + case .m_getDownloadTasksForCourse__courseId: return ".getDownloadTasksForCourse(_:)" + case .m_cancelDownloading__courseId_courseIdblocks_blocks: return ".cancelDownloading(courseId:blocks:)" + case .m_cancelDownloading__task_task: return ".cancelDownloading(task:)" + case .m_cancelDownloading__courseId_courseId: return ".cancelDownloading(courseId:)" + case .m_cancelAllDownloading: return ".cancelAllDownloading()" + case .m_deleteAll: return ".deleteAll()" + case .m_fileUrl__for_blockId: return ".fileUrl(for:)" + case .m_resumeDownloading: return ".resumeDownloading()" + case .m_isLargeVideosSize__blocks_blocks: return ".isLargeVideosSize(blocks:)" + case .m_removeAppSupportDirectoryUnusedContent: return ".removeAppSupportDirectoryUnusedContent()" + case .m_delete__blocks_blockscourseId_courseId: return ".delete(blocks:courseId:)" + case .m_downloadTask__for_blockId: return ".downloadTask(for:)" + case .m_getFreeDiskSpace: return ".getFreeDiskSpace()" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + + public static func getCurrentDownloadTask(willReturn: DownloadDataTask?...) -> MethodStub { + return Given(method: .m_getCurrentDownloadTask, products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func eventPublisher(willReturn: AnyPublisher...) -> MethodStub { + return Given(method: .m_eventPublisher, products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getDownloadTasks(willReturn: [DownloadDataTask]...) -> MethodStub { + return Given(method: .m_getDownloadTasks, products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getDownloadTasksForCourse(_ courseId: Parameter, willReturn: [DownloadDataTask]...) -> MethodStub { + return Given(method: .m_getDownloadTasksForCourse__courseId(`courseId`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func fileUrl(for blockId: Parameter, willReturn: URL?...) -> MethodStub { + return Given(method: .m_fileUrl__for_blockId(`blockId`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func isLargeVideosSize(blocks: Parameter<[CourseBlock]>, willReturn: Bool...) -> MethodStub { + return Given(method: .m_isLargeVideosSize__blocks_blocks(`blocks`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func downloadTask(for blockId: Parameter, willReturn: DownloadDataTask?...) -> MethodStub { + return Given(method: .m_downloadTask__for_blockId(`blockId`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getFreeDiskSpace(willReturn: Int?...) -> MethodStub { + return Given(method: .m_getFreeDiskSpace, products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getCurrentDownloadTask(willProduce: (Stubber) -> Void) -> MethodStub { + let willReturn: [DownloadDataTask?] = [] + let given: Given = { return Given(method: .m_getCurrentDownloadTask, products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (DownloadDataTask?).self) + willProduce(stubber) + return given + } + public static func eventPublisher(willProduce: (Stubber>) -> Void) -> MethodStub { + let willReturn: [AnyPublisher] = [] + let given: Given = { return Given(method: .m_eventPublisher, products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (AnyPublisher).self) + willProduce(stubber) + return given + } + public static func getDownloadTasks(willProduce: (Stubber<[DownloadDataTask]>) -> Void) -> MethodStub { + let willReturn: [[DownloadDataTask]] = [] + let given: Given = { return Given(method: .m_getDownloadTasks, products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: ([DownloadDataTask]).self) + willProduce(stubber) + return given + } + public static func getDownloadTasksForCourse(_ courseId: Parameter, willProduce: (Stubber<[DownloadDataTask]>) -> Void) -> MethodStub { + let willReturn: [[DownloadDataTask]] = [] + let given: Given = { return Given(method: .m_getDownloadTasksForCourse__courseId(`courseId`), products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: ([DownloadDataTask]).self) + willProduce(stubber) + return given + } + public static func fileUrl(for blockId: Parameter, willProduce: (Stubber) -> Void) -> MethodStub { + let willReturn: [URL?] = [] + let given: Given = { return Given(method: .m_fileUrl__for_blockId(`blockId`), products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (URL?).self) + willProduce(stubber) + return given + } + public static func isLargeVideosSize(blocks: Parameter<[CourseBlock]>, willProduce: (Stubber) -> Void) -> MethodStub { + let willReturn: [Bool] = [] + let given: Given = { return Given(method: .m_isLargeVideosSize__blocks_blocks(`blocks`), products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (Bool).self) + willProduce(stubber) + return given + } + public static func downloadTask(for blockId: Parameter, willProduce: (Stubber) -> Void) -> MethodStub { + let willReturn: [DownloadDataTask?] = [] + let given: Given = { return Given(method: .m_downloadTask__for_blockId(`blockId`), products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (DownloadDataTask?).self) + willProduce(stubber) + return given + } + public static func getFreeDiskSpace(willProduce: (Stubber) -> Void) -> MethodStub { + let willReturn: [Int?] = [] + let given: Given = { return Given(method: .m_getFreeDiskSpace, products: willReturn.map({ StubProduct.return($0 as Any) })) }() + let stubber = given.stub(for: (Int?).self) + willProduce(stubber) + return given + } + public static func addToDownloadQueue(blocks: Parameter<[CourseBlock]>, willThrow: Error...) -> MethodStub { + return Given(method: .m_addToDownloadQueue__blocks_blocks(`blocks`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func addToDownloadQueue(blocks: Parameter<[CourseBlock]>, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_addToDownloadQueue__blocks_blocks(`blocks`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } + public static func cancelDownloading(courseId: Parameter, blocks: Parameter<[CourseBlock]>, willThrow: Error...) -> MethodStub { + return Given(method: .m_cancelDownloading__courseId_courseIdblocks_blocks(`courseId`, `blocks`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func cancelDownloading(courseId: Parameter, blocks: Parameter<[CourseBlock]>, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_cancelDownloading__courseId_courseIdblocks_blocks(`courseId`, `blocks`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } + public static func cancelDownloading(task: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_cancelDownloading__task_task(`task`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func cancelDownloading(task: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_cancelDownloading__task_task(`task`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } + public static func cancelDownloading(courseId: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_cancelDownloading__courseId_courseId(`courseId`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func cancelDownloading(courseId: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_cancelDownloading__courseId_courseId(`courseId`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } + public static func cancelAllDownloading(willThrow: Error...) -> MethodStub { + return Given(method: .m_cancelAllDownloading, products: willThrow.map({ StubProduct.throw($0) })) + } + public static func cancelAllDownloading(willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_cancelAllDownloading, products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } + public static func resumeDownloading(willThrow: Error...) -> MethodStub { + return Given(method: .m_resumeDownloading, products: willThrow.map({ StubProduct.throw($0) })) + } + public static func resumeDownloading(willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_resumeDownloading, products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } + } + + public struct Verify { + fileprivate var method: MethodType + + public static func getCurrentDownloadTask() -> Verify { return Verify(method: .m_getCurrentDownloadTask)} + public static func eventPublisher() -> Verify { return Verify(method: .m_eventPublisher)} + public static func addToDownloadQueue(blocks: Parameter<[CourseBlock]>) -> Verify { return Verify(method: .m_addToDownloadQueue__blocks_blocks(`blocks`))} + public static func getDownloadTasks() -> Verify { return Verify(method: .m_getDownloadTasks)} + public static func getDownloadTasksForCourse(_ courseId: Parameter) -> Verify { return Verify(method: .m_getDownloadTasksForCourse__courseId(`courseId`))} + public static func cancelDownloading(courseId: Parameter, blocks: Parameter<[CourseBlock]>) -> Verify { return Verify(method: .m_cancelDownloading__courseId_courseIdblocks_blocks(`courseId`, `blocks`))} + public static func cancelDownloading(task: Parameter) -> Verify { return Verify(method: .m_cancelDownloading__task_task(`task`))} + public static func cancelDownloading(courseId: Parameter) -> Verify { return Verify(method: .m_cancelDownloading__courseId_courseId(`courseId`))} + public static func cancelAllDownloading() -> Verify { return Verify(method: .m_cancelAllDownloading)} + public static func deleteAll() -> Verify { return Verify(method: .m_deleteAll)} + public static func fileUrl(for blockId: Parameter) -> Verify { return Verify(method: .m_fileUrl__for_blockId(`blockId`))} + public static func resumeDownloading() -> Verify { return Verify(method: .m_resumeDownloading)} + public static func isLargeVideosSize(blocks: Parameter<[CourseBlock]>) -> Verify { return Verify(method: .m_isLargeVideosSize__blocks_blocks(`blocks`))} + public static func removeAppSupportDirectoryUnusedContent() -> Verify { return Verify(method: .m_removeAppSupportDirectoryUnusedContent)} + public static func delete(blocks: Parameter<[CourseBlock]>, courseId: Parameter) -> Verify { return Verify(method: .m_delete__blocks_blockscourseId_courseId(`blocks`, `courseId`))} + public static func downloadTask(for blockId: Parameter) -> Verify { return Verify(method: .m_downloadTask__for_blockId(`blockId`))} + public static func getFreeDiskSpace() -> Verify { return Verify(method: .m_getFreeDiskSpace)} + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func getCurrentDownloadTask(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_getCurrentDownloadTask, performs: perform) + } + public static func eventPublisher(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_eventPublisher, performs: perform) + } + public static func addToDownloadQueue(blocks: Parameter<[CourseBlock]>, perform: @escaping ([CourseBlock]) -> Void) -> Perform { + return Perform(method: .m_addToDownloadQueue__blocks_blocks(`blocks`), performs: perform) + } + public static func getDownloadTasks(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_getDownloadTasks, performs: perform) + } + public static func getDownloadTasksForCourse(_ courseId: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_getDownloadTasksForCourse__courseId(`courseId`), performs: perform) + } + public static func cancelDownloading(courseId: Parameter, blocks: Parameter<[CourseBlock]>, perform: @escaping (String, [CourseBlock]) -> Void) -> Perform { + return Perform(method: .m_cancelDownloading__courseId_courseIdblocks_blocks(`courseId`, `blocks`), performs: perform) + } + public static func cancelDownloading(task: Parameter, perform: @escaping (DownloadDataTask) -> Void) -> Perform { + return Perform(method: .m_cancelDownloading__task_task(`task`), performs: perform) + } + public static func cancelDownloading(courseId: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_cancelDownloading__courseId_courseId(`courseId`), performs: perform) + } + public static func cancelAllDownloading(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_cancelAllDownloading, performs: perform) + } + public static func deleteAll(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_deleteAll, performs: perform) + } + public static func fileUrl(for blockId: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_fileUrl__for_blockId(`blockId`), performs: perform) + } + public static func resumeDownloading(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_resumeDownloading, performs: perform) + } + public static func isLargeVideosSize(blocks: Parameter<[CourseBlock]>, perform: @escaping ([CourseBlock]) -> Void) -> Perform { + return Perform(method: .m_isLargeVideosSize__blocks_blocks(`blocks`), performs: perform) + } + public static func removeAppSupportDirectoryUnusedContent(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_removeAppSupportDirectoryUnusedContent, performs: perform) + } + public static func delete(blocks: Parameter<[CourseBlock]>, courseId: Parameter, perform: @escaping ([CourseBlock], String) -> Void) -> Perform { + return Perform(method: .m_delete__blocks_blockscourseId_courseId(`blocks`, `courseId`), performs: perform) + } + public static func downloadTask(for blockId: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_downloadTask__for_blockId(`blockId`), performs: perform) + } + public static func getFreeDiskSpace(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_getFreeDiskSpace, performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + +// MARK: - OfflineSyncInteractorProtocol + +open class OfflineSyncInteractorProtocolMock: OfflineSyncInteractorProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + + + + + open func submitOfflineProgress(courseID: String, blockID: String, data: String) throws -> Bool { + addInvocation(.m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(Parameter.value(`courseID`), Parameter.value(`blockID`), Parameter.value(`data`))) + let perform = methodPerformValue(.m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(Parameter.value(`courseID`), Parameter.value(`blockID`), Parameter.value(`data`))) as? (String, String, String) -> Void + perform?(`courseID`, `blockID`, `data`) + var __value: Bool + do { + __value = try methodReturnValue(.m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(Parameter.value(`courseID`), Parameter.value(`blockID`), Parameter.value(`data`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for submitOfflineProgress(courseID: String, blockID: String, data: String). Use given") + Failure("Stub return value not specified for submitOfflineProgress(courseID: String, blockID: String, data: String). Use given") + } catch { + throw error + } + return __value + } + + + fileprivate enum MethodType { + case m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(Parameter, Parameter, Parameter) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(let lhsCourseid, let lhsBlockid, let lhsData), .m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(let rhsCourseid, let rhsBlockid, let rhsData)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBlockid, rhs: rhsBlockid, with: matcher), lhsBlockid, rhsBlockid, "blockID")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsData, rhs: rhsData, with: matcher), lhsData, rhsData, "data")) + return Matcher.ComparisonResult(results) + } + } + + func intValue() -> Int { + switch self { + case let .m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue + } + } + func assertionName() -> String { + switch self { + case .m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data: return ".submitOfflineProgress(courseID:blockID:data:)" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + + public static func submitOfflineProgress(courseID: Parameter, blockID: Parameter, data: Parameter, willReturn: Bool...) -> MethodStub { + return Given(method: .m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(`courseID`, `blockID`, `data`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func submitOfflineProgress(courseID: Parameter, blockID: Parameter, data: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(`courseID`, `blockID`, `data`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func submitOfflineProgress(courseID: Parameter, blockID: Parameter, data: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(`courseID`, `blockID`, `data`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Bool).self) + willProduce(stubber) + return given + } + } + + public struct Verify { + fileprivate var method: MethodType + + public static func submitOfflineProgress(courseID: Parameter, blockID: Parameter, data: Parameter) -> Verify { return Verify(method: .m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(`courseID`, `blockID`, `data`))} + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func submitOfflineProgress(courseID: Parameter, blockID: Parameter, data: Parameter, perform: @escaping (String, String, String) -> Void) -> Perform { + return Perform(method: .m_submitOfflineProgress__courseID_courseIDblockID_blockIDdata_data(`courseID`, `blockID`, `data`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + diff --git a/AppDates/AppDatesTests/AppDatesTests.swift b/AppDates/AppDatesTests/AppDatesTests.swift new file mode 100644 index 000000000..4e8946e2a --- /dev/null +++ b/AppDates/AppDatesTests/AppDatesTests.swift @@ -0,0 +1,371 @@ +// +// AppDatesTests.swift +// AppDatesTests +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import XCTest +import SwiftyMocky +@testable import AppDates +@testable import Core +import SwiftUI + +@MainActor +final class DatesViewModelTests: XCTestCase { + + func testLoadDatesSuccess() async throws { + // Arrange + let interactor = DatesInteractorProtocolMock() + let connectivity = ConnectivityProtocolMock() + let analytics = AppDatesAnalyticsMock() + let courseManager = CourseStructureManagerProtocolMock() + let router = AppDatesRouterMock() + + let viewModel = DatesViewModel( + interactor: interactor, + connectivity: connectivity, + courseManager: courseManager, + analytics: analytics, + router: router + ) + + let date = Date() + let courseDate1 = CourseDate( + date: date.addingTimeInterval(60 * 60 * 24), // tomorrow + title: "Test title 1", + courseName: "Test Course 1", + courseId: "course-123", + blockId: "block-123", + hasAccess: true + ) + + let courseDate2 = CourseDate( + date: date.addingTimeInterval(60 * 60 * 24 * 7), // next week + title: "Test title 2", + courseName: "Test Course 2", + courseId: "course-456", + blockId: "block-456", + hasAccess: true + ) + + Given(connectivity, .isInternetAvaliable(getter: true)) + Given(interactor, .getCourseDatesOffline(limit: .value(20), offset: .value(0), willReturn: [courseDate1, courseDate2])) + Given(interactor, .getCourseDates(page: .value(1), willReturn: ([courseDate1, courseDate2], nil))) + + // Act + await viewModel.loadDates() + + // Assert + Verify(interactor, 1, .getCourseDatesOffline(limit: .value(20), offset: .value(0))) + Verify(interactor, 1, .getCourseDates(page: .value(1))) + + XCTAssertEqual(viewModel.coursesDates.count, 2) + XCTAssertFalse(viewModel.isShowProgress) + XCTAssertFalse(viewModel.noDates) + XCTAssertNil(viewModel.errorMessage) + XCTAssertFalse(viewModel.showError) + } + + func testLoadDatesOfflineSuccess() async throws { + // Arrange + let interactor = DatesInteractorProtocolMock() + let connectivity = ConnectivityProtocolMock() + let analytics = AppDatesAnalyticsMock() + let courseManager = CourseStructureManagerProtocolMock() + let router = AppDatesRouterMock() + + let viewModel = DatesViewModel( + interactor: interactor, + connectivity: connectivity, + courseManager: courseManager, + analytics: analytics, + router: router + ) + + let date = Date() + let courseDate1 = CourseDate( + date: date.addingTimeInterval(-60 * 60 * 24), // yesterday (pastDue) + title: "Test title 1", + courseName: "Test Course 1", + courseId: "course-123", + blockId: "block-123", + hasAccess: true + ) + + let courseDate2 = CourseDate( + date: date.addingTimeInterval(60 * 60), // later today (not past due) + title: "Test title 2", + courseName: "Test Course 2", + courseId: "course-456", + blockId: "block-456", + hasAccess: true + ) + + Given(connectivity, .isInternetAvaliable(getter: false)) + Given(interactor, .getCourseDatesOffline(limit: .value(nil), offset: .value(nil), willReturn: [courseDate1, courseDate2])) + + // Act + await viewModel.loadDates() + + // Assert + Verify(interactor, 1, .getCourseDatesOffline(limit: .value(nil), offset: .value(nil))) + + XCTAssertEqual(viewModel.coursesDates.count, 2) + XCTAssertFalse(viewModel.isShowProgress) + XCTAssertFalse(viewModel.noDates) + XCTAssertNil(viewModel.errorMessage) + XCTAssertFalse(viewModel.showError) + XCTAssertTrue(viewModel.showShiftDueDatesView) + } + + func testLoadDatesNoCachedDataError() async throws { + // Arrange + let interactor = DatesInteractorProtocolMock() + let connectivity = ConnectivityProtocolMock() + let analytics = AppDatesAnalyticsMock() + let courseManager = CourseStructureManagerProtocolMock() + let router = AppDatesRouterMock() + + let viewModel = DatesViewModel( + interactor: interactor, + connectivity: connectivity, + courseManager: courseManager, + analytics: analytics, + router: router + ) + + Given(connectivity, .isInternetAvaliable(getter: true)) + Given(interactor, .getCourseDatesOffline(limit: .any, offset: .any, willThrow: NoCachedDataError())) + + // Act + await viewModel.loadDates() + + // Assert + Verify(interactor, 1, .getCourseDatesOffline(limit: .value(20), offset: .value(0))) + + XCTAssertTrue(viewModel.coursesDates.isEmpty) + XCTAssertFalse(viewModel.isShowProgress) + XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.slowOrNoInternetConnection) + XCTAssertTrue(viewModel.showError) + } + + func testLoadDatesUnknownError() async throws { + // Arrange + let interactor = DatesInteractorProtocolMock() + let connectivity = ConnectivityProtocolMock() + let analytics = AppDatesAnalyticsMock() + let courseManager = CourseStructureManagerProtocolMock() + let router = AppDatesRouterMock() + + let viewModel = DatesViewModel( + interactor: interactor, + connectivity: connectivity, + courseManager: courseManager, + analytics: analytics, + router: router + ) + + Given(connectivity, .isInternetAvaliable(getter: true)) + Given(interactor, .getCourseDatesOffline(limit: .any, offset: .any, willThrow: NSError(domain: "error", code: -1, userInfo: nil))) + + // Act + await viewModel.loadDates() + + // Assert + Verify(interactor, 1, .getCourseDatesOffline(limit: .value(20), offset: .value(0))) + + XCTAssertTrue(viewModel.coursesDates.isEmpty) + XCTAssertFalse(viewModel.isShowProgress) + XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.unknownError) + XCTAssertTrue(viewModel.showError) + } + + func testLoadNextPage() async throws { + // Arrange + let interactor = DatesInteractorProtocolMock() + let connectivity = ConnectivityProtocolMock() + let analytics = AppDatesAnalyticsMock() + let courseManager = CourseStructureManagerProtocolMock() + let router = AppDatesRouterMock() + + let viewModel = DatesViewModel( + interactor: interactor, + connectivity: connectivity, + courseManager: courseManager, + analytics: analytics, + router: router + ) + + let date = Date() + let dates = (0...22).map { i in + CourseDate( + date: date.addingTimeInterval(Double(i) * 60 * 60 * 24), + title: "Test title \(i)", + courseName: "Test Course \(i)", + courseId: "course-\(i)", + blockId: "block-\(i)", + hasAccess: true + ) + } + + Given(connectivity, .isInternetAvaliable(getter: true)) + Given(interactor, .getCourseDatesOffline(limit: .value(20), offset: .value(0), willReturn: Array(dates.prefix(20)))) + Given(interactor, .getCourseDates(page: .any, willReturn: (dates, "next-page"))) + + // Act + await viewModel.loadDates() + await viewModel.loadNextPageIfNeeded(for: dates[17]) + + // Assert + Verify(interactor, 1, .getCourseDatesOffline(limit: .value(20), offset: .value(0))) + Verify(interactor, 1, .getCourseDates(page: .value(1))) + + XCTAssertFalse(viewModel.isLoadingNextPage) + XCTAssertFalse(viewModel.isShowProgress) + XCTAssertFalse(viewModel.delayedLoadSecondPage) + } + + func testLoadNextPageDelayedLoad() async throws { + // Arrange + let interactor = DatesInteractorProtocolMock() + let connectivity = ConnectivityProtocolMock() + let analytics = AppDatesAnalyticsMock() + let courseManager = CourseStructureManagerProtocolMock() + let router = AppDatesRouterMock() + + let viewModel = DatesViewModel( + interactor: interactor, + connectivity: connectivity, + courseManager: courseManager, + analytics: analytics, + router: router + ) + + let date = Date() + let dates = (0...22).map { i in + CourseDate( + date: date.addingTimeInterval(Double(i) * 60 * 60 * 24), + title: "Test title \(i)", + courseName: "Test Course \(i)", + courseId: "course-\(i)", + blockId: "block-\(i)", + hasAccess: true + ) + } + + Given(connectivity, .isInternetAvaliable(getter: true)) + Given(interactor, .getCourseDatesOffline(limit: .value(20), offset: .value(0), willReturn: Array(dates.prefix(20)))) + Given(interactor, .getCourseDates(page: .any, willReturn: (dates, "next-page"))) + + // Act + await viewModel.loadDates() + await viewModel.loadNextPageIfNeeded(for: dates[17]) + XCTAssertFalse(viewModel.fetchInProgress) + } + + func testShiftDueDates() async throws { + // Arrange + let interactor = DatesInteractorProtocolMock() + let connectivity = ConnectivityProtocolMock() + let analytics = AppDatesAnalyticsMock() + let courseManager = CourseStructureManagerProtocolMock() + let router = AppDatesRouterMock() + + let viewModel = DatesViewModel( + interactor: interactor, + connectivity: connectivity, + courseManager: courseManager, + analytics: analytics, + router: router + ) + + // Setup past due date + let date = Date() + let pastDueDate = CourseDate( + date: date.addingTimeInterval(-60 * 60 * 24), // yesterday + title: "Past Due", + courseName: "Test Course", + courseId: "course-123", + blockId: "block-123", + hasAccess: true + ) + + Given(connectivity, .isInternetAvaliable(getter: true)) + Given(interactor, .resetAllRelativeCourseDeadlines(willProduce: {_ in })) + Given(interactor, .getCourseDates(page: .value(1), willReturn: ([], nil))) + Given(interactor, .getCourseDatesOffline(limit: .value(20), offset: .value(0), willReturn: [])) + + // Setup coursesDates with a pastDue group + viewModel.coursesDates = [DateGroup(type: .pastDue, dates: [pastDueDate])] + + // Act + await viewModel.shiftDueDates() + + // Assert + Verify(interactor, 1, .resetAllRelativeCourseDeadlines()) + + XCTAssertFalse(viewModel.isShowProgressForDueDates) + XCTAssertFalse(viewModel.showShiftDueDatesView) + } + + func testShiftDueDatesWithError() async throws { + // Arrange + let interactor = DatesInteractorProtocolMock() + let connectivity = ConnectivityProtocolMock() + let analytics = AppDatesAnalyticsMock() + let courseManager = CourseStructureManagerProtocolMock() + let router = AppDatesRouterMock() + + let viewModel = DatesViewModel( + interactor: interactor, + connectivity: connectivity, + courseManager: courseManager, + analytics: analytics, + router: router + ) + + // Setup past due date + let date = Date() + let pastDueDate = CourseDate( + date: date.addingTimeInterval(-60 * 60 * 24), // yesterday + title: "Past Due", + courseName: "Test Course", + courseId: "course-123", + blockId: "block-123", + hasAccess: true + ) + + // Setup coursesDates with a pastDue group + viewModel.coursesDates = [DateGroup(type: .pastDue, dates: [pastDueDate])] + + // Test internet error + Given(connectivity, .isInternetAvaliable(getter: true)) + Given(interactor, .resetAllRelativeCourseDeadlines(willThrow: NoCachedDataError())) + + // Act + await viewModel.shiftDueDates() + + // Assert + Verify(interactor, 1, .resetAllRelativeCourseDeadlines()) + XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.slowOrNoInternetConnection) + XCTAssertTrue(viewModel.showError) + XCTAssertFalse(viewModel.isShowProgressForDueDates) + + // Reset state + viewModel.errorMessage = nil + viewModel.showError = false + + // Test unknown error + Given(interactor, .resetAllRelativeCourseDeadlines(willThrow: NSError(domain: "error", code: -1, userInfo: nil))) + + // Act + await viewModel.shiftDueDates() + + // Assert + Verify(interactor, 2, .resetAllRelativeCourseDeadlines()) + XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.unknownError) + XCTAssertTrue(viewModel.showError) + XCTAssertFalse(viewModel.isShowProgressForDueDates) + } +} diff --git a/AppDates/Mockfile b/AppDates/Mockfile new file mode 100644 index 000000000..940ff582b --- /dev/null +++ b/AppDates/Mockfile @@ -0,0 +1,18 @@ +sourceryCommand: mint run krzysztofzablocki/Sourcery@2.1.2 sourcery +sourceryTemplate: ../MockTemplate.swifttemplate +unit.tests.mock: + sources: + include: + - ./../Core + - ./AppDates + exclude: [] + output: ./AppDatesTests/AppDatesMock.generated.swift + targets: + - MyAppUnitTests + import: + - Core + - AppDates + - Foundation + - SwiftUI + - Combine + - OEXFoundation \ No newline at end of file diff --git a/AppDates/swiftgen.yml b/AppDates/swiftgen.yml new file mode 100644 index 000000000..66b3aaee3 --- /dev/null +++ b/AppDates/swiftgen.yml @@ -0,0 +1,9 @@ +strings: + inputs: + - AppDates/en.lproj + outputs: + - templateName: structured-swift5 + params: + publicAccess: true + enumName: AppDatesLocalization + output: AppDates/SwiftGen/Strings.swift diff --git a/Authorization/Authorization.xcodeproj/project.pbxproj b/Authorization/Authorization.xcodeproj/project.pbxproj index 8473bbbf8..97ff10c93 100644 --- a/Authorization/Authorization.xcodeproj/project.pbxproj +++ b/Authorization/Authorization.xcodeproj/project.pbxproj @@ -526,14 +526,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Authorization-AuthorizationTests/Pods-App-Authorization-AuthorizationTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Authorization-AuthorizationTests/Pods-App-Authorization-AuthorizationTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App-Authorization-AuthorizationTests/Pods-App-Authorization-AuthorizationTests-resources.sh\"\n"; diff --git a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift index 68502311d..d77065633 100644 --- a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift +++ b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift @@ -4318,10 +4318,24 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M return __value } + open func shiftDueDates(courseID: String) throws { + addInvocation(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + do { + _ = try methodReturnValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + fileprivate enum MethodType { case m_getCourseBlocks__courseID_courseID(Parameter) case m_getLoadedCourseBlocks__courseID_courseID(Parameter) + case m_shiftDueDates__courseID_courseID(Parameter) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -4334,6 +4348,11 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) + + case (.m_shiftDueDates__courseID_courseID(let lhsCourseid), .m_shiftDueDates__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) default: return .none } } @@ -4342,12 +4361,14 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M switch self { case let .m_getCourseBlocks__courseID_courseID(p0): return p0.intValue case let .m_getLoadedCourseBlocks__courseID_courseID(p0): return p0.intValue + case let .m_shiftDueDates__courseID_courseID(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_getCourseBlocks__courseID_courseID: return ".getCourseBlocks(courseID:)" case .m_getLoadedCourseBlocks__courseID_courseID: return ".getLoadedCourseBlocks(courseID:)" + case .m_shiftDueDates__courseID_courseID: return ".shiftDueDates(courseID:)" } } } @@ -4387,6 +4408,16 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M willProduce(stubber) return given } + public static func shiftDueDates(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func shiftDueDates(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } } public struct Verify { @@ -4394,6 +4425,7 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocks__courseID_courseID(`courseID`))} public static func getLoadedCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`))} + public static func shiftDueDates(courseID: Parameter) -> Verify { return Verify(method: .m_shiftDueDates__courseID_courseID(`courseID`))} } public struct Perform { @@ -4406,6 +4438,9 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getLoadedCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), performs: perform) } + public static func shiftDueDates(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_shiftDueDates__courseID_courseID(`courseID`), performs: perform) + } } public func given(_ method: Given) { diff --git a/Core/Core.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Core/Core.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7dde19a07..8fea13f3b 100644 --- a/Core/Core.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Core/Core.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,14 +1,78 @@ { + "originHash" : "cff1ce99f935bf284704df70f99c3965fa4c70c7593d750d65bccdf61d0d15b0", "pins" : [ { - "identity" : "youtubeplayerkit", + "identity" : "alamofire", "kind" : "remoteSourceControl", - "location" : "https://github.com/SvenTiigi/YouTubePlayerKit", + "location" : "https://github.com/Alamofire/Alamofire.git", "state" : { - "revision" : "1fe4c8b07a61d50c2fd276e1d9c8087583c7638a", - "version" : "1.5.3" + "revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5", + "version" : "5.10.2" + } + }, + { + "identity" : "keychain-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/evgenyneu/keychain-swift.git", + "state" : { + "revision" : "5e1b02b6a9dac2a759a1d5dbc175c86bd192a608", + "version" : "24.0.0" + } + }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "2015fda791daa72c8058619545a593bf8c1dd59f", + "version" : "8.5.0" + } + }, + { + "identity" : "openedx-app-foundation-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/openedx/openedx-app-foundation-ios/", + "state" : { + "revision" : "61259adc491ad73689c5731cfd0af5142198bd73", + "version" : "1.0.4" + } + }, + { + "identity" : "swiftlintplugins", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SimplyDanny/SwiftLintPlugins", + "state" : { + "revision" : "6166499ede0efe43c3809f101d2c3cd409e2b77a", + "version" : "0.61.0" + } + }, + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/swiftui-introspect", + "state" : { + "revision" : "a08b87f96b41055577721a6e397562b21ad52454", + "version" : "26.0.0" + } + }, + { + "identity" : "swinject", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Swinject/Swinject.git", + "state" : { + "revision" : "b685b549fe4d8ae265fc7a2f27d0789720425d69", + "version" : "2.10.0" + } + }, + { + "identity" : "ziparchive", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ZipArchive/ZipArchive.git", + "state" : { + "revision" : "df35718ea19a94e015b91dc4881dee028ce4cdba", + "version" : "2.6.0" } } ], - "version" : 2 + "version" : 3 } diff --git a/Core/Core/Analytics/CoreAnalytics.swift b/Core/Core/Analytics/CoreAnalytics.swift index 06848c09e..3f6315800 100644 --- a/Core/Core/Analytics/CoreAnalytics.swift +++ b/Core/Core/Analytics/CoreAnalytics.swift @@ -75,10 +75,12 @@ public enum AnalyticsEvent: String { case mainDiscoveryTabClicked = "MainDashboard:Discover" case mainDashboardLearnTabClicked = "MainDashboard:Learn" case mainDownloadsTabClicked = "MainDashboard:Downloads" + case mainDatesTabClicked = "MainDates:Downloads" case mainProfileTabClicked = "MainDashboard:Profile" case mainProgramsTabClicked = "MainDashboard:My Programs" case mainDashboardCoursesClicked = "Learn:My Courses" case mainDashboardProgramsClicked = "Learn:My Programs" + case mainDashboardDatesTabClicked = "MainDashboard:Dates" case discoverySearchBarClicked = "Discovery:Search Bar Clicked" case discoveryCoursesSearch = "Discovery:Courses Search" case discoveryCourseClicked = "Discovery:Course Clicked" @@ -176,6 +178,9 @@ public enum AnalyticsEvent: String { case discussionLikeToggle = "Discussion:Like Toggle" case discussionReportToggle = "Discussion:Report Toggle" case notificationSettingPermissionStatus = "Notification:Setting Permission Status" + case datesCourseClicked = "Dates:Course Clicked" + case datesSettingsClicked = "Dates:Settings Clicked" + case datesRefreshPulled = "Dates:Refresh Pulled" case downloadsScreenViewed = "Downloads:Screen Viewed" case downloadCourseClicked = "Downloads:Download Course Clicked" case cancelDownloadClicked = "Downloads:Cancel Download Clicked" @@ -211,8 +216,10 @@ public enum EventBIValue: String { case mainDashboardLearnTabClicked = "edx.bi.app.main_dashboard.learn" case mainDashboardCoursesClicked = "edx.bi.app.main_dashboard.learn.my_course" case mainDashboardProgramsClicked = "edx.bi.app.main_dashboard.learn.my_programs" + case mainDashboardDatesTabClicked = "edx.bi.app.main_dashboard.discover.dates" case mainProgramsTabClicked = "edx.bi.app.main_dashboard.my_program" case mainDownloadsTabClicked = "edx.bi.app.main_dashboard.downloads" + case mainDatesTabClicked = "edx.bi.app.main_dashboard.dates" case mainProfileTabClicked = "edx.bi.app.main_dashboard.profile" case profileEditClicked = "edx.bi.app.profile.edit.clicked" case profileEditDoneClicked = "edx.bi.app.profile.edit_done.clicked" @@ -306,6 +313,9 @@ public enum EventBIValue: String { case discussionLikeToggle = "edx.bi.app.discussion.like_toggle" case discussionReportToggle = "edx.bi.app.discussion.report_toggle" case notificationSettingPermissionStatus = "edx.bi.app.notification.setting_permission.status" + case datesCourseClicked = "edx.bi.app.dates.course.clicked" + case datesSettingsClicked = "edx.bi.app.dates.settings.clicked" + case datesRefreshPulled = "edx.bi.app.dates.refresh.pulled" case downloadsScreenViewed = "edx.bi.app.downloads.screen_viewed" case downloadCourseClicked = "edx.bi.app.downloads.download_course.clicked" case cancelDownloadClicked = "edx.bi.app.downloads.cancel_download.clicked" diff --git a/Core/Core/Assets.xcassets/CourseNavigationBar/dates.imageset/Contents.json b/Core/Core/Assets.xcassets/CourseNavigationBar/dates.imageset/Contents.json index 10d2b3951..1982f8c47 100644 --- a/Core/Core/Assets.xcassets/CourseNavigationBar/dates.imageset/Contents.json +++ b/Core/Core/Assets.xcassets/CourseNavigationBar/dates.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Core/Core/Configuration/Config/ExperimentalFeaturesConfig.swift b/Core/Core/Configuration/Config/ExperimentalFeaturesConfig.swift index 2e18e51a3..cbe158cc5 100644 --- a/Core/Core/Configuration/Config/ExperimentalFeaturesConfig.swift +++ b/Core/Core/Configuration/Config/ExperimentalFeaturesConfig.swift @@ -9,6 +9,7 @@ import Foundation private enum ExperimentalFeaturesKeys: String { case appLevelDownloads = "APP_LEVEL_DOWNLOADS" + case appLevelDates = "APP_LEVEL_DATES" } private enum FeatureKeys: String { @@ -17,6 +18,7 @@ private enum FeatureKeys: String { public final class ExperimentalFeaturesConfig: NSObject { public var appLevelDownloadsEnabled: Bool = false + public var appLevelDatesEnabled: Bool = false init(dictionary: [String: AnyObject]) { super.init() @@ -24,6 +26,10 @@ public final class ExperimentalFeaturesConfig: NSObject { let isEnabled = downloadsDict[FeatureKeys.enabled.rawValue] as? Bool { appLevelDownloadsEnabled = isEnabled } + if let downloadsDict = dictionary[ExperimentalFeaturesKeys.appLevelDates.rawValue] as? [String: AnyObject], + let isEnabled = downloadsDict[FeatureKeys.enabled.rawValue] as? Bool { + appLevelDatesEnabled = isEnabled + } } } diff --git a/Core/Core/Domain/CourseStructureManagerProtocol.swift b/Core/Core/Domain/CourseStructureManagerProtocol.swift index 047d05ef6..72002aa5e 100644 --- a/Core/Core/Domain/CourseStructureManagerProtocol.swift +++ b/Core/Core/Domain/CourseStructureManagerProtocol.swift @@ -11,6 +11,7 @@ import Foundation public protocol CourseStructureManagerProtocol: Sendable { func getCourseBlocks(courseID: String) async throws -> CourseStructure func getLoadedCourseBlocks(courseID: String) async throws -> CourseStructure + func shiftDueDates(courseID: String) async throws } #if DEBUG @@ -61,5 +62,7 @@ public actor CourseStructureManagerMock: CourseStructureManagerProtocol { courseProgress: nil ) } + + public func shiftDueDates(courseID: String) async throws {} } #endif diff --git a/Core/Core/Extensions/ViewExtension.swift b/Core/Core/Extensions/ViewExtension.swift index 92766f5e6..f41f1449d 100644 --- a/Core/Core/Extensions/ViewExtension.swift +++ b/Core/Core/Extensions/ViewExtension.swift @@ -130,6 +130,14 @@ public extension View { } } } + + func refreshableWithoutCancellation(action: @escaping () async -> Void) -> some View { + self.refreshable { + await Task { + await action() + }.value + } + } } public extension View { diff --git a/Core/Core/SwiftGen/Strings.swift b/Core/Core/SwiftGen/Strings.swift index c832d6c7e..5054dda3d 100644 --- a/Core/Core/SwiftGen/Strings.swift +++ b/Core/Core/SwiftGen/Strings.swift @@ -163,8 +163,8 @@ public enum CoreLocalization { /// Course Dates public static let title = CoreLocalization.tr("Localizable", "COURSE_DATES.RESET_DATE.TITLE", fallback: "Course Dates") public enum ResetDateBanner { - /// Don't worry - shift our suggested schedule to complete past due assignments without losing any progress. - public static let body = CoreLocalization.tr("Localizable", "COURSE_DATES.RESET_DATE.RESET_DATE_BANNER.BODY", fallback: "Don't worry - shift our suggested schedule to complete past due assignments without losing any progress.") + /// Don't worry - shift our suggested schedule to complete the due assignments without losing any progress. + public static let body = CoreLocalization.tr("Localizable", "COURSE_DATES.RESET_DATE.RESET_DATE_BANNER.BODY", fallback: "Don't worry - shift our suggested schedule to complete the due assignments without losing any progress.") /// Shift due dates public static let button = CoreLocalization.tr("Localizable", "COURSE_DATES.RESET_DATE.RESET_DATE_BANNER.BUTTON", fallback: "Shift due dates") /// Missed some deadlines? diff --git a/Core/Core/en.lproj/Localizable.strings b/Core/Core/en.lproj/Localizable.strings index 07f823907..28a93c8d9 100644 --- a/Core/Core/en.lproj/Localizable.strings +++ b/Core/Core/en.lproj/Localizable.strings @@ -137,7 +137,7 @@ "OPEN_IN_BROWSER"="View in Safari"; -"COURSE_DATES.RESET_DATE.RESET_DATE_BANNER.BODY" = "Don't worry - shift our suggested schedule to complete past due assignments without losing any progress."; +"COURSE_DATES.RESET_DATE.RESET_DATE_BANNER.BODY" = "Don't worry - shift our suggested schedule to complete the due assignments without losing any progress."; "COURSE_DATES.RESET_DATE.RESET_DATE_BANNER.BUTTON" = "Shift due dates"; "COURSE_DATES.RESET_DATE.RESET_DATE_BANNER.HEADER" = "Missed some deadlines?"; diff --git a/Core/CoreTests/CoreMock.generated.swift b/Core/CoreTests/CoreMock.generated.swift index f6b71ea0c..dcc937551 100644 --- a/Core/CoreTests/CoreMock.generated.swift +++ b/Core/CoreTests/CoreMock.generated.swift @@ -3485,10 +3485,24 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M return __value } + open func shiftDueDates(courseID: String) throws { + addInvocation(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + do { + _ = try methodReturnValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + fileprivate enum MethodType { case m_getCourseBlocks__courseID_courseID(Parameter) case m_getLoadedCourseBlocks__courseID_courseID(Parameter) + case m_shiftDueDates__courseID_courseID(Parameter) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -3501,6 +3515,11 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) + + case (.m_shiftDueDates__courseID_courseID(let lhsCourseid), .m_shiftDueDates__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) default: return .none } } @@ -3509,12 +3528,14 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M switch self { case let .m_getCourseBlocks__courseID_courseID(p0): return p0.intValue case let .m_getLoadedCourseBlocks__courseID_courseID(p0): return p0.intValue + case let .m_shiftDueDates__courseID_courseID(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_getCourseBlocks__courseID_courseID: return ".getCourseBlocks(courseID:)" case .m_getLoadedCourseBlocks__courseID_courseID: return ".getLoadedCourseBlocks(courseID:)" + case .m_shiftDueDates__courseID_courseID: return ".shiftDueDates(courseID:)" } } } @@ -3554,6 +3575,16 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M willProduce(stubber) return given } + public static func shiftDueDates(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func shiftDueDates(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } } public struct Verify { @@ -3561,6 +3592,7 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocks__courseID_courseID(`courseID`))} public static func getLoadedCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`))} + public static func shiftDueDates(courseID: Parameter) -> Verify { return Verify(method: .m_shiftDueDates__courseID_courseID(`courseID`))} } public struct Perform { @@ -3573,6 +3605,9 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getLoadedCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), performs: perform) } + public static func shiftDueDates(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_shiftDueDates__courseID_courseID(`courseID`), performs: perform) + } } public func given(_ method: Given) { diff --git a/Course/Course.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Course/Course.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bc46758e1..48e4ec69b 100644 --- a/Course/Course.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Course/Course.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,78 +1,78 @@ { - "originHash" : "4160ad5c384a202ab7fadacdde1b48c0804f37461b3e6c1594017ed3fa34f043", - "pins" : [ + "originHash": "4160ad5c384a202ab7fadacdde1b48c0804f37461b3e6c1594017ed3fa34f043", + "pins": [ { - "identity" : "alamofire", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Alamofire/Alamofire.git", - "state" : { - "revision" : "e16d3481f5ed35f0472cb93350085853d754913f", - "version" : "5.10.1" + "identity": "alamofire", + "kind": "remoteSourceControl", + "location": "https://github.com/Alamofire/Alamofire.git", + "state": { + "revision": "e16d3481f5ed35f0472cb93350085853d754913f", + "version": "5.10.1" } }, { - "identity" : "keychain-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/evgenyneu/keychain-swift.git", - "state" : { - "revision" : "5e1b02b6a9dac2a759a1d5dbc175c86bd192a608", - "version" : "24.0.0" + "identity": "keychain-swift", + "kind": "remoteSourceControl", + "location": "https://github.com/evgenyneu/keychain-swift.git", + "state": { + "revision": "5e1b02b6a9dac2a759a1d5dbc175c86bd192a608", + "version": "24.0.0" } }, { - "identity" : "kingfisher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/onevcat/Kingfisher.git", - "state" : { - "revision" : "c0940e241945e6378c01fbd45fd3815579d47ef5", - "version" : "8.1.0" + "identity": "kingfisher", + "kind": "remoteSourceControl", + "location": "https://github.com/onevcat/Kingfisher.git", + "state": { + "revision": "c0940e241945e6378c01fbd45fd3815579d47ef5", + "version": "8.1.0" } }, { - "identity" : "openedx-app-foundation-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/openedx/openedx-app-foundation-ios/", - "state" : { - "revision" : "09e601a74dd68cffddf0055061c71aa5979c4a64", - "version" : "1.0.3" + "identity": "openedx-app-foundation-ios", + "kind": "remoteSourceControl", + "location": "https://github.com/openedx/openedx-app-foundation-ios/", + "state": { + "revision": "61259adc491ad73689c5731cfd0af5142198bd73", + "version": "1.0.4" } }, { - "identity" : "swiftlintplugins", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SimplyDanny/SwiftLintPlugins", - "state" : { - "revision" : "7c80ce6f142164b0201871e580b021d1b2c69804", - "version" : "0.57.0" + "identity": "swiftlintplugins", + "kind": "remoteSourceControl", + "location": "https://github.com/SimplyDanny/SwiftLintPlugins", + "state": { + "revision": "7c80ce6f142164b0201871e580b021d1b2c69804", + "version": "0.57.0" } }, { - "identity" : "swiftui-introspect", - "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/swiftui-introspect", - "state" : { - "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336", - "version" : "1.3.0" + "identity": "swiftui-introspect", + "kind": "remoteSourceControl", + "location": "https://github.com/siteline/swiftui-introspect", + "state": { + "revision": "807f73ce09a9b9723f12385e592b4e0aaebd3336", + "version": "1.3.0" } }, { - "identity" : "swinject", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Swinject/Swinject.git", - "state" : { - "revision" : "be9dbcc7b86811bc131539a20c6f9c2d3e56919f", - "version" : "2.9.1" + "identity": "swinject", + "kind": "remoteSourceControl", + "location": "https://github.com/Swinject/Swinject.git", + "state": { + "revision": "be9dbcc7b86811bc131539a20c6f9c2d3e56919f", + "version": "2.9.1" } }, { - "identity" : "youtubeplayerkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SvenTiigi/YouTubePlayerKit", - "state" : { - "revision" : "fe1c1ec340f6d79866131432ecaa190fd6bbc4cb", - "version" : "1.9.0" + "identity": "youtubeplayerkit", + "kind": "remoteSourceControl", + "location": "https://github.com/SvenTiigi/YouTubePlayerKit", + "state": { + "revision": "fe1c1ec340f6d79866131432ecaa190fd6bbc4cb", + "version": "1.9.0" } } ], - "version" : 3 + "version": 3 } diff --git a/Course/CourseTests/CourseMock.generated.swift b/Course/CourseTests/CourseMock.generated.swift index 31c6dc09e..60be340b6 100644 --- a/Course/CourseTests/CourseMock.generated.swift +++ b/Course/CourseTests/CourseMock.generated.swift @@ -6005,10 +6005,24 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M return __value } + open func shiftDueDates(courseID: String) throws { + addInvocation(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + do { + _ = try methodReturnValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + fileprivate enum MethodType { case m_getCourseBlocks__courseID_courseID(Parameter) case m_getLoadedCourseBlocks__courseID_courseID(Parameter) + case m_shiftDueDates__courseID_courseID(Parameter) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -6021,6 +6035,11 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) + + case (.m_shiftDueDates__courseID_courseID(let lhsCourseid), .m_shiftDueDates__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) default: return .none } } @@ -6029,12 +6048,14 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M switch self { case let .m_getCourseBlocks__courseID_courseID(p0): return p0.intValue case let .m_getLoadedCourseBlocks__courseID_courseID(p0): return p0.intValue + case let .m_shiftDueDates__courseID_courseID(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_getCourseBlocks__courseID_courseID: return ".getCourseBlocks(courseID:)" case .m_getLoadedCourseBlocks__courseID_courseID: return ".getLoadedCourseBlocks(courseID:)" + case .m_shiftDueDates__courseID_courseID: return ".shiftDueDates(courseID:)" } } } @@ -6074,6 +6095,16 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M willProduce(stubber) return given } + public static func shiftDueDates(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func shiftDueDates(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } } public struct Verify { @@ -6081,6 +6112,7 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocks__courseID_courseID(`courseID`))} public static func getLoadedCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`))} + public static func shiftDueDates(courseID: Parameter) -> Verify { return Verify(method: .m_shiftDueDates__courseID_courseID(`courseID`))} } public struct Perform { @@ -6093,6 +6125,9 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getLoadedCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), performs: perform) } + public static func shiftDueDates(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_shiftDueDates__courseID_courseID(`courseID`), performs: perform) + } } public func given(_ method: Given) { diff --git a/Dashboard/Dashboard.xcodeproj/project.pbxproj b/Dashboard/Dashboard.xcodeproj/project.pbxproj index dc5f31b57..48dda4c64 100644 --- a/Dashboard/Dashboard.xcodeproj/project.pbxproj +++ b/Dashboard/Dashboard.xcodeproj/project.pbxproj @@ -477,14 +477,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Dashboard-DashboardTests/Pods-App-Dashboard-DashboardTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Dashboard-DashboardTests/Pods-App-Dashboard-DashboardTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App-Dashboard-DashboardTests/Pods-App-Dashboard-DashboardTests-resources.sh\"\n"; diff --git a/Dashboard/Dashboard/Presentation/PrimaryCourseDashboardView.swift b/Dashboard/Dashboard/Presentation/PrimaryCourseDashboardView.swift index 905dcaaae..19489db1c 100644 --- a/Dashboard/Dashboard/Presentation/PrimaryCourseDashboardView.swift +++ b/Dashboard/Dashboard/Presentation/PrimaryCourseDashboardView.swift @@ -323,8 +323,7 @@ public struct PrimaryCourseDashboardView: View { .listRowBackground(Color.clear) .padding(.horizontal, 20) - .accessibilityElement(children: .ignore) - .accessibilityLabel(DashboardLocalization.Header.courses + DashboardLocalization.Header.welcomeBack) + .accessibilityElement(children: .contain) } } } diff --git a/Dashboard/DashboardTests/DashboardMock.generated.swift b/Dashboard/DashboardTests/DashboardMock.generated.swift index 7f9150efc..80add5014 100644 --- a/Dashboard/DashboardTests/DashboardMock.generated.swift +++ b/Dashboard/DashboardTests/DashboardMock.generated.swift @@ -3487,10 +3487,24 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M return __value } + open func shiftDueDates(courseID: String) throws { + addInvocation(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + do { + _ = try methodReturnValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + fileprivate enum MethodType { case m_getCourseBlocks__courseID_courseID(Parameter) case m_getLoadedCourseBlocks__courseID_courseID(Parameter) + case m_shiftDueDates__courseID_courseID(Parameter) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -3503,6 +3517,11 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) + + case (.m_shiftDueDates__courseID_courseID(let lhsCourseid), .m_shiftDueDates__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) default: return .none } } @@ -3511,12 +3530,14 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M switch self { case let .m_getCourseBlocks__courseID_courseID(p0): return p0.intValue case let .m_getLoadedCourseBlocks__courseID_courseID(p0): return p0.intValue + case let .m_shiftDueDates__courseID_courseID(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_getCourseBlocks__courseID_courseID: return ".getCourseBlocks(courseID:)" case .m_getLoadedCourseBlocks__courseID_courseID: return ".getLoadedCourseBlocks(courseID:)" + case .m_shiftDueDates__courseID_courseID: return ".shiftDueDates(courseID:)" } } } @@ -3556,6 +3577,16 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M willProduce(stubber) return given } + public static func shiftDueDates(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func shiftDueDates(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } } public struct Verify { @@ -3563,6 +3594,7 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocks__courseID_courseID(`courseID`))} public static func getLoadedCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`))} + public static func shiftDueDates(courseID: Parameter) -> Verify { return Verify(method: .m_shiftDueDates__courseID_courseID(`courseID`))} } public struct Perform { @@ -3575,6 +3607,9 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getLoadedCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), performs: perform) } + public static func shiftDueDates(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_shiftDueDates__courseID_courseID(`courseID`), performs: perform) + } } public func given(_ method: Given) { diff --git a/Discovery/Discovery.xcodeproj/project.pbxproj b/Discovery/Discovery.xcodeproj/project.pbxproj index 9669212f1..60ea0f9e1 100644 --- a/Discovery/Discovery.xcodeproj/project.pbxproj +++ b/Discovery/Discovery.xcodeproj/project.pbxproj @@ -518,14 +518,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Discovery-DiscoveryUnitTests/Pods-App-Discovery-DiscoveryUnitTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Discovery-DiscoveryUnitTests/Pods-App-Discovery-DiscoveryUnitTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App-Discovery-DiscoveryUnitTests/Pods-App-Discovery-DiscoveryUnitTests-resources.sh\"\n"; diff --git a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift index ce86689bf..67602250f 100644 --- a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift +++ b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift @@ -3487,10 +3487,24 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M return __value } + open func shiftDueDates(courseID: String) throws { + addInvocation(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + do { + _ = try methodReturnValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + fileprivate enum MethodType { case m_getCourseBlocks__courseID_courseID(Parameter) case m_getLoadedCourseBlocks__courseID_courseID(Parameter) + case m_shiftDueDates__courseID_courseID(Parameter) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -3503,6 +3517,11 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) + + case (.m_shiftDueDates__courseID_courseID(let lhsCourseid), .m_shiftDueDates__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) default: return .none } } @@ -3511,12 +3530,14 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M switch self { case let .m_getCourseBlocks__courseID_courseID(p0): return p0.intValue case let .m_getLoadedCourseBlocks__courseID_courseID(p0): return p0.intValue + case let .m_shiftDueDates__courseID_courseID(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_getCourseBlocks__courseID_courseID: return ".getCourseBlocks(courseID:)" case .m_getLoadedCourseBlocks__courseID_courseID: return ".getLoadedCourseBlocks(courseID:)" + case .m_shiftDueDates__courseID_courseID: return ".shiftDueDates(courseID:)" } } } @@ -3556,6 +3577,16 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M willProduce(stubber) return given } + public static func shiftDueDates(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func shiftDueDates(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } } public struct Verify { @@ -3563,6 +3594,7 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocks__courseID_courseID(`courseID`))} public static func getLoadedCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`))} + public static func shiftDueDates(courseID: Parameter) -> Verify { return Verify(method: .m_shiftDueDates__courseID_courseID(`courseID`))} } public struct Perform { @@ -3575,6 +3607,9 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getLoadedCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), performs: perform) } + public static func shiftDueDates(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_shiftDueDates__courseID_courseID(`courseID`), performs: perform) + } } public func given(_ method: Given) { diff --git a/Discussion/Discussion.xcodeproj/project.pbxproj b/Discussion/Discussion.xcodeproj/project.pbxproj index 3daa42fdb..d77f1bcb9 100644 --- a/Discussion/Discussion.xcodeproj/project.pbxproj +++ b/Discussion/Discussion.xcodeproj/project.pbxproj @@ -668,14 +668,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Discussion-DiscussionTests/Pods-App-Discussion-DiscussionTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Discussion-DiscussionTests/Pods-App-Discussion-DiscussionTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App-Discussion-DiscussionTests/Pods-App-Discussion-DiscussionTests-resources.sh\"\n"; @@ -971,6 +967,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Discussion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1005,6 +1002,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Discussion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1102,6 +1100,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Discussion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1200,6 +1199,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Discussion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1292,6 +1292,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Discussion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1383,6 +1384,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Discussion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1606,6 +1608,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Discussion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1719,6 +1722,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Discussion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; diff --git a/Discussion/DiscussionTests/DiscussionMock.generated.swift b/Discussion/DiscussionTests/DiscussionMock.generated.swift index 1fcf6a9e1..78a221249 100644 --- a/Discussion/DiscussionTests/DiscussionMock.generated.swift +++ b/Discussion/DiscussionTests/DiscussionMock.generated.swift @@ -3487,10 +3487,24 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M return __value } + open func shiftDueDates(courseID: String) throws { + addInvocation(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + do { + _ = try methodReturnValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + fileprivate enum MethodType { case m_getCourseBlocks__courseID_courseID(Parameter) case m_getLoadedCourseBlocks__courseID_courseID(Parameter) + case m_shiftDueDates__courseID_courseID(Parameter) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -3503,6 +3517,11 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) + + case (.m_shiftDueDates__courseID_courseID(let lhsCourseid), .m_shiftDueDates__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) default: return .none } } @@ -3511,12 +3530,14 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M switch self { case let .m_getCourseBlocks__courseID_courseID(p0): return p0.intValue case let .m_getLoadedCourseBlocks__courseID_courseID(p0): return p0.intValue + case let .m_shiftDueDates__courseID_courseID(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_getCourseBlocks__courseID_courseID: return ".getCourseBlocks(courseID:)" case .m_getLoadedCourseBlocks__courseID_courseID: return ".getLoadedCourseBlocks(courseID:)" + case .m_shiftDueDates__courseID_courseID: return ".shiftDueDates(courseID:)" } } } @@ -3556,6 +3577,16 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M willProduce(stubber) return given } + public static func shiftDueDates(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func shiftDueDates(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } } public struct Verify { @@ -3563,6 +3594,7 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocks__courseID_courseID(`courseID`))} public static func getLoadedCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`))} + public static func shiftDueDates(courseID: Parameter) -> Verify { return Verify(method: .m_shiftDueDates__courseID_courseID(`courseID`))} } public struct Perform { @@ -3575,6 +3607,9 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getLoadedCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), performs: perform) } + public static func shiftDueDates(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_shiftDueDates__courseID_courseID(`courseID`), performs: perform) + } } public func given(_ method: Given) { diff --git a/Downloads/Downloads.xcodeproj/project.pbxproj b/Downloads/Downloads.xcodeproj/project.pbxproj index 2907f8e3d..410444bc5 100644 --- a/Downloads/Downloads.xcodeproj/project.pbxproj +++ b/Downloads/Downloads.xcodeproj/project.pbxproj @@ -434,14 +434,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Downloads-DownloadsTests/Pods-App-Downloads-DownloadsTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Downloads-DownloadsTests/Pods-App-Downloads-DownloadsTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App-Downloads-DownloadsTests/Pods-App-Downloads-DownloadsTests-resources.sh\"\n"; diff --git a/Downloads/Downloads/Presentation/AppDownloadsView.swift b/Downloads/Downloads/Presentation/AppDownloadsView.swift index 13ff61eb7..0598ec2bd 100644 --- a/Downloads/Downloads/Presentation/AppDownloadsView.swift +++ b/Downloads/Downloads/Presentation/AppDownloadsView.swift @@ -182,8 +182,7 @@ public struct AppDownloadsView: View { } .listRowBackground(Color.clear) .padding(.horizontal, 20) - .accessibilityElement(children: .ignore) - .accessibilityLabel(DownloadsLocalization.Downloads.title) + .accessibilityElement(children: .contain) } } diff --git a/Downloads/DownloadsTests/Downloads Mock.generated.swift b/Downloads/DownloadsTests/Downloads Mock.generated.swift index d9e8cc845..3b877c26c 100644 --- a/Downloads/DownloadsTests/Downloads Mock.generated.swift +++ b/Downloads/DownloadsTests/Downloads Mock.generated.swift @@ -3487,10 +3487,24 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M return __value } + open func shiftDueDates(courseID: String) throws { + addInvocation(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + do { + _ = try methodReturnValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + fileprivate enum MethodType { case m_getCourseBlocks__courseID_courseID(Parameter) case m_getLoadedCourseBlocks__courseID_courseID(Parameter) + case m_shiftDueDates__courseID_courseID(Parameter) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -3503,6 +3517,11 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) + + case (.m_shiftDueDates__courseID_courseID(let lhsCourseid), .m_shiftDueDates__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) default: return .none } } @@ -3511,12 +3530,14 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M switch self { case let .m_getCourseBlocks__courseID_courseID(p0): return p0.intValue case let .m_getLoadedCourseBlocks__courseID_courseID(p0): return p0.intValue + case let .m_shiftDueDates__courseID_courseID(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_getCourseBlocks__courseID_courseID: return ".getCourseBlocks(courseID:)" case .m_getLoadedCourseBlocks__courseID_courseID: return ".getLoadedCourseBlocks(courseID:)" + case .m_shiftDueDates__courseID_courseID: return ".shiftDueDates(courseID:)" } } } @@ -3556,6 +3577,16 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M willProduce(stubber) return given } + public static func shiftDueDates(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func shiftDueDates(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } } public struct Verify { @@ -3563,6 +3594,7 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocks__courseID_courseID(`courseID`))} public static func getLoadedCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`))} + public static func shiftDueDates(courseID: Parameter) -> Verify { return Verify(method: .m_shiftDueDates__courseID_courseID(`courseID`))} } public struct Perform { @@ -3575,6 +3607,9 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getLoadedCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), performs: perform) } + public static func shiftDueDates(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_shiftDueDates__courseID_courseID(`courseID`), performs: perform) + } } public func given(_ method: Given) { diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index ddfa0c30b..cf0f567ee 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -60,11 +60,14 @@ CE1D5B7B2CE60E000019CA34 /* ContainerMainActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1D5B7A2CE60E000019CA34 /* ContainerMainActor.swift */; }; CE3BD14E2CBEB0DA0026F4E3 /* PluginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3BD14D2CBEB0DA0026F4E3 /* PluginManager.swift */; }; CE57127A2CD109A800D4AB17 /* OEXFoundation in Embed Frameworks */ = {isa = PBXBuildFile; productRef = CE9C07D72CD104E5009C44D1 /* OEXFoundation */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + CE815A992D60DA9300CB9114 /* AppDates.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE815A982D60DA9300CB9114 /* AppDates.framework */; }; + CE815A9A2D60DA9300CB9114 /* AppDates.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE815A982D60DA9300CB9114 /* AppDates.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CE924BE72CD8FAB3000137CA /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = CE924BE62CD8FAB3000137CA /* FirebaseMessaging */; }; CE9C07D82CD104E5009C44D1 /* OEXFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = CE9C07D72CD104E5009C44D1 /* OEXFoundation */; }; CEB36E522D6A29CE00907A89 /* Downloads.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEB36E512D6A29CE00907A89 /* Downloads.framework */; }; CEB36E532D6A29CE00907A89 /* Downloads.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEB36E512D6A29CE00907A89 /* Downloads.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CEBA52772CEBB69100619E2B /* OEXFirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = CEBA52762CEBB69100619E2B /* OEXFirebaseAnalytics */; }; + CED29EAD2D91D88E00836226 /* DatesPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED29EAC2D91D88E00836226 /* DatesPersistence.swift */; }; CEE5EDEE2D6E0A290089F67C /* DownloadsPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE5EDED2D6E0A290089F67C /* DownloadsPersistence.swift */; }; E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; }; E0D6E6A42B1626D60089F9C9 /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -84,6 +87,7 @@ 07A7D79028F5C9060000BE81 /* Core.framework in Embed Frameworks */, CE57127A2CD109A800D4AB17 /* OEXFoundation in Embed Frameworks */, 0770DE4C28D0A462006D8A5D /* Authorization.framework in Embed Frameworks */, + CE815A9A2D60DA9300CB9114 /* AppDates.framework in Embed Frameworks */, 0219C67828F4347600D64452 /* Course.framework in Embed Frameworks */, 025DE1A528DB4DAE0053E0F4 /* Profile.framework in Embed Frameworks */, 027DB33128D8A063002B6862 /* Dashboard.framework in Embed Frameworks */, @@ -150,7 +154,9 @@ CCE1E0F850D3E25C0D6C6702 /* Pods-App-OpenEdX.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.debugdev.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.debugdev.xcconfig"; sourceTree = ""; }; CE1D5B7A2CE60E000019CA34 /* ContainerMainActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerMainActor.swift; sourceTree = ""; }; CE3BD14D2CBEB0DA0026F4E3 /* PluginManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginManager.swift; sourceTree = ""; }; + CE815A982D60DA9300CB9114 /* AppDates.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AppDates.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CEB36E512D6A29CE00907A89 /* Downloads.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Downloads.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CED29EAC2D91D88E00836226 /* DatesPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatesPersistence.swift; sourceTree = ""; }; CEE5EDED2D6E0A290089F67C /* DownloadsPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsPersistence.swift; sourceTree = ""; }; D70D30110012B7D52D05E876 /* Pods-App-OpenEdX.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.debugprod.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.debugprod.xcconfig"; sourceTree = ""; }; E0D6E6A22B1626B10089F9C9 /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -162,6 +168,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CE815A992D60DA9300CB9114 /* AppDates.framework in Frameworks */, E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */, 07A7D78F28F5C9060000BE81 /* Core.framework in Frameworks */, 028A37362ADFF404008CA604 /* WhatsNew.framework in Frameworks */, @@ -190,6 +197,7 @@ 025AD4AB2A6FB95C00AB8FA7 /* DatabaseManager.swift */, 0293A2022A6FCA590090A336 /* CorePersistence.swift */, 0293A2042A6FCD430090A336 /* CoursePersistence.swift */, + CED29EAC2D91D88E00836226 /* DatesPersistence.swift */, 0293A2062A6FCDA30090A336 /* DiscoveryPersistence.swift */, 0293A2082A6FCDE50090A336 /* DashboardPersistence.swift */, CEE5EDED2D6E0A290089F67C /* DownloadsPersistence.swift */, @@ -269,6 +277,7 @@ 4E6FB43543890E90BB88D64D /* Frameworks */ = { isa = PBXGroup; children = ( + CE815A982D60DA9300CB9114 /* AppDates.framework */, CEB36E512D6A29CE00907A89 /* Downloads.framework */, E0D6E6A22B1626B10089F9C9 /* Theme.framework */, 028A37352ADFF404008CA604 /* WhatsNew.framework */, @@ -565,6 +574,7 @@ 020CA5D92AA0A25300970AAF /* AppStorage.swift in Sources */, 0298DF302A4EF7230023A257 /* AnalyticsManager.swift in Sources */, 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */, + CED29EAD2D91D88E00836226 /* DatesPersistence.swift in Sources */, 07D5DA3528D075AA00752FD9 /* AppDelegate.swift in Sources */, 02F175312A4DA95B0019CD70 /* MainScreenAnalytics.swift in Sources */, BA7468762B96201D00793145 /* DeepLinkRouter.swift in Sources */, @@ -701,7 +711,7 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = L8PG7LC3Y3; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; @@ -793,7 +803,7 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = L8PG7LC3Y3; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; @@ -891,7 +901,7 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = L8PG7LC3Y3; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; @@ -983,7 +993,7 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = L8PG7LC3Y3; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; @@ -1135,7 +1145,7 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = L8PG7LC3Y3; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; @@ -1173,7 +1183,7 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = L8PG7LC3Y3; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; diff --git a/OpenEdX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OpenEdX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d47a36499..de56fd23a 100644 --- a/OpenEdX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OpenEdX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,204 +1,204 @@ { - "originHash" : "679c9881c55ee7e1f6d683f7cf7aae4fd8777f05e38f2cce8c6d21aa216ec03b", - "pins" : [ + "originHash": "679c9881c55ee7e1f6d683f7cf7aae4fd8777f05e38f2cce8c6d21aa216ec03b", + "pins": [ { - "identity" : "abseil-cpp-binary", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/abseil-cpp-binary.git", - "state" : { - "revision" : "194a6706acbd25e4ef639bcaddea16e8758a3e27", - "version" : "1.2024011602.0" + "identity": "abseil-cpp-binary", + "kind": "remoteSourceControl", + "location": "https://github.com/google/abseil-cpp-binary.git", + "state": { + "revision": "194a6706acbd25e4ef639bcaddea16e8758a3e27", + "version": "1.2024011602.0" } }, { - "identity" : "alamofire", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Alamofire/Alamofire.git", - "state" : { - "revision" : "e16d3481f5ed35f0472cb93350085853d754913f", - "version" : "5.10.1" + "identity": "alamofire", + "kind": "remoteSourceControl", + "location": "https://github.com/Alamofire/Alamofire.git", + "state": { + "revision": "e16d3481f5ed35f0472cb93350085853d754913f", + "version": "5.10.1" } }, { - "identity" : "app-check", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/app-check.git", - "state" : { - "revision" : "87dd288fc792bf9751e522e171a47df5b783b0b8", - "version" : "11.1.0" + "identity": "app-check", + "kind": "remoteSourceControl", + "location": "https://github.com/google/app-check.git", + "state": { + "revision": "87dd288fc792bf9751e522e171a47df5b783b0b8", + "version": "11.1.0" } }, { - "identity" : "firebase-ios-sdk", - "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/firebase-ios-sdk", - "state" : { - "revision" : "f909f901bfba9e27e4e9da83242a4915d6dd64bb", - "version" : "11.3.0" + "identity": "firebase-ios-sdk", + "kind": "remoteSourceControl", + "location": "https://github.com/firebase/firebase-ios-sdk", + "state": { + "revision": "f909f901bfba9e27e4e9da83242a4915d6dd64bb", + "version": "11.3.0" } }, { - "identity" : "googleappmeasurement", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GoogleAppMeasurement.git", - "state" : { - "revision" : "93406fd21b85e66e2d6dbf50b472161fd75c3f1f", - "version" : "11.3.0" + "identity": "googleappmeasurement", + "kind": "remoteSourceControl", + "location": "https://github.com/google/GoogleAppMeasurement.git", + "state": { + "revision": "93406fd21b85e66e2d6dbf50b472161fd75c3f1f", + "version": "11.3.0" } }, { - "identity" : "googledatatransport", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GoogleDataTransport.git", - "state" : { - "revision" : "617af071af9aa1d6a091d59a202910ac482128f9", - "version" : "10.1.0" + "identity": "googledatatransport", + "kind": "remoteSourceControl", + "location": "https://github.com/google/GoogleDataTransport.git", + "state": { + "revision": "617af071af9aa1d6a091d59a202910ac482128f9", + "version": "10.1.0" } }, { - "identity" : "googleutilities", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GoogleUtilities.git", - "state" : { - "revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb", - "version" : "8.0.2" + "identity": "googleutilities", + "kind": "remoteSourceControl", + "location": "https://github.com/google/GoogleUtilities.git", + "state": { + "revision": "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb", + "version": "8.0.2" } }, { - "identity" : "grpc-binary", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/grpc-binary.git", - "state" : { - "revision" : "f56d8fc3162de9a498377c7b6cea43431f4f5083", - "version" : "1.65.1" + "identity": "grpc-binary", + "kind": "remoteSourceControl", + "location": "https://github.com/google/grpc-binary.git", + "state": { + "revision": "f56d8fc3162de9a498377c7b6cea43431f4f5083", + "version": "1.65.1" } }, { - "identity" : "gtm-session-fetcher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/gtm-session-fetcher.git", - "state" : { - "revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b", - "version" : "3.5.0" + "identity": "gtm-session-fetcher", + "kind": "remoteSourceControl", + "location": "https://github.com/google/gtm-session-fetcher.git", + "state": { + "revision": "a2ab612cb980066ee56d90d60d8462992c07f24b", + "version": "3.5.0" } }, { - "identity" : "interop-ios-for-google-sdks", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/interop-ios-for-google-sdks.git", - "state" : { - "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", - "version" : "100.0.0" + "identity": "interop-ios-for-google-sdks", + "kind": "remoteSourceControl", + "location": "https://github.com/google/interop-ios-for-google-sdks.git", + "state": { + "revision": "2d12673670417654f08f5f90fdd62926dc3a2648", + "version": "100.0.0" } }, { - "identity" : "keychain-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/evgenyneu/keychain-swift.git", - "state" : { - "revision" : "5e1b02b6a9dac2a759a1d5dbc175c86bd192a608", - "version" : "24.0.0" + "identity": "keychain-swift", + "kind": "remoteSourceControl", + "location": "https://github.com/evgenyneu/keychain-swift.git", + "state": { + "revision": "5e1b02b6a9dac2a759a1d5dbc175c86bd192a608", + "version": "24.0.0" } }, { - "identity" : "kingfisher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/onevcat/Kingfisher.git", - "state" : { - "revision" : "c0940e241945e6378c01fbd45fd3815579d47ef5", - "version" : "8.1.0" + "identity": "kingfisher", + "kind": "remoteSourceControl", + "location": "https://github.com/onevcat/Kingfisher.git", + "state": { + "revision": "c0940e241945e6378c01fbd45fd3815579d47ef5", + "version": "8.1.0" } }, { - "identity" : "leveldb", - "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/leveldb.git", - "state" : { - "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", - "version" : "1.22.5" + "identity": "leveldb", + "kind": "remoteSourceControl", + "location": "https://github.com/firebase/leveldb.git", + "state": { + "revision": "a0bc79961d7be727d258d33d5a6b2f1023270ba1", + "version": "1.22.5" } }, { - "identity" : "microsoft-authentication-library-for-objc", - "kind" : "remoteSourceControl", - "location" : "https://github.com/AzureAD/microsoft-authentication-library-for-objc", - "state" : { - "revision" : "332bc5a808099da0ee82c27eefa57f763bc76fd5", - "version" : "1.8.1" + "identity": "microsoft-authentication-library-for-objc", + "kind": "remoteSourceControl", + "location": "https://github.com/AzureAD/microsoft-authentication-library-for-objc", + "state": { + "revision": "332bc5a808099da0ee82c27eefa57f763bc76fd5", + "version": "1.8.1" } }, { - "identity" : "nanopb", - "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/nanopb.git", - "state" : { - "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", - "version" : "2.30910.0" + "identity": "nanopb", + "kind": "remoteSourceControl", + "location": "https://github.com/firebase/nanopb.git", + "state": { + "revision": "b7e1104502eca3a213b46303391ca4d3bc8ddec1", + "version": "2.30910.0" } }, { - "identity" : "openedx-app-firebase-analytics-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/openedx/openedx-app-firebase-analytics-ios", - "state" : { - "revision" : "99d440b38ad73d3d829c8ec669c03acbf95bbb6b", - "version" : "1.0.3" + "identity": "openedx-app-firebase-analytics-ios", + "kind": "remoteSourceControl", + "location": "https://github.com/openedx/openedx-app-firebase-analytics-ios", + "state": { + "revision": "99d440b38ad73d3d829c8ec669c03acbf95bbb6b", + "version": "1.0.3" } }, { - "identity" : "openedx-app-foundation-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/openedx/openedx-app-foundation-ios/", - "state" : { - "revision" : "09e601a74dd68cffddf0055061c71aa5979c4a64", - "version" : "1.0.3" + "identity": "openedx-app-foundation-ios", + "kind": "remoteSourceControl", + "location": "https://github.com/openedx/openedx-app-foundation-ios/", + "state": { + "revision": "61259adc491ad73689c5731cfd0af5142198bd73", + "version": "1.0.4" } }, { - "identity" : "promises", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/promises.git", - "state" : { - "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", - "version" : "2.4.0" + "identity": "promises", + "kind": "remoteSourceControl", + "location": "https://github.com/google/promises.git", + "state": { + "revision": "540318ecedd63d883069ae7f1ed811a2df00b6ac", + "version": "2.4.0" } }, { - "identity" : "swift-protobuf", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-protobuf.git", - "state" : { - "revision" : "ebc7251dd5b37f627c93698e4374084d98409633", - "version" : "1.28.2" + "identity": "swift-protobuf", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-protobuf.git", + "state": { + "revision": "ebc7251dd5b37f627c93698e4374084d98409633", + "version": "1.28.2" } }, { - "identity" : "swiftlintplugins", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SimplyDanny/SwiftLintPlugins", - "state" : { - "revision" : "7c80ce6f142164b0201871e580b021d1b2c69804", - "version" : "0.57.0" + "identity": "swiftlintplugins", + "kind": "remoteSourceControl", + "location": "https://github.com/SimplyDanny/SwiftLintPlugins", + "state": { + "revision": "7c80ce6f142164b0201871e580b021d1b2c69804", + "version": "0.57.0" } }, { - "identity" : "swiftui-introspect", - "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/swiftui-introspect", - "state" : { - "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336", - "version" : "1.3.0" + "identity": "swiftui-introspect", + "kind": "remoteSourceControl", + "location": "https://github.com/siteline/swiftui-introspect", + "state": { + "revision": "807f73ce09a9b9723f12385e592b4e0aaebd3336", + "version": "1.3.0" } }, { - "identity" : "swinject", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Swinject/Swinject.git", - "state" : { - "revision" : "be9dbcc7b86811bc131539a20c6f9c2d3e56919f", - "version" : "2.9.1" + "identity": "swinject", + "kind": "remoteSourceControl", + "location": "https://github.com/Swinject/Swinject.git", + "state": { + "revision": "be9dbcc7b86811bc131539a20c6f9c2d3e56919f", + "version": "2.9.1" } } ], - "version" : 3 + "version": 3 } diff --git a/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme b/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme index bf00190a1..a26a07720 100644 --- a/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme +++ b/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme @@ -145,6 +145,16 @@ ReferencedContainer = "container:Core/Core.xcodeproj"> + + + + + + diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index f90b6a500..996c7b92b 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -18,6 +18,7 @@ import Authorization import Downloads import Profile import WhatsNew +import AppDates // swiftlint:disable function_body_length class AppAssembly: Assembly { @@ -83,6 +84,10 @@ class AppAssembly: Assembly { r.resolve(AnalyticsManager.self)! }.inObjectScope(.container) + container.register(AppDatesAnalytics.self) { r in + r.resolve(AnalyticsManager.self)! + }.inObjectScope(.container) + container.register(DownloadsAnalytics.self) { r in r.resolve(AnalyticsManager.self)! }.inObjectScope(.container) @@ -114,6 +119,10 @@ class AppAssembly: Assembly { container.register(AuthorizationRouter.self) { r in r.resolve(Router.self)! }.inObjectScope(.container) + + container.register(AppDatesRouter.self) { r in + r.resolve(Router.self)! + }.inObjectScope(.container) container.register(DiscoveryRouter.self) { r in r.resolve(Router.self)! diff --git a/OpenEdX/DI/ScreenAssembly.swift b/OpenEdX/DI/ScreenAssembly.swift index 75dce720d..7d3a3e619 100644 --- a/OpenEdX/DI/ScreenAssembly.swift +++ b/OpenEdX/DI/ScreenAssembly.swift @@ -17,6 +17,7 @@ import Downloads import Profile import Course import Discussion +import AppDates @preconcurrency import Combine // swiftlint:disable function_body_length closure_parameter_position type_body_length @@ -317,6 +318,42 @@ class ScreenAssembly: Assembly { ) } + // MARK: AppDates + container.register(DatesPersistenceProtocol.self) { r in + DatesPersistence(container: r.resolve(DatabaseManager.self)!.getPersistentContainer()) + } + + container.register(DatesRepositoryProtocol.self) { r in + DatesRepository( + api: r.resolve(API.self)!, + storage: r.resolve(CoreStorage.self)!, + config: r.resolve(ConfigProtocol.self)!, + persistence: r.resolve(DatesPersistenceProtocol.self)! + ) + } + + container.register(CourseStructureManagerProtocol.self) { r in + CourseInteractor( + repository: r.resolve(CourseRepositoryProtocol.self)! + ) + } + + container.register(DatesInteractorProtocol.self) { r in + DatesInteractor( + repository: r.resolve(DatesRepositoryProtocol.self)! + ) + } + + container.register(DatesViewModel.self) { @MainActor r in + DatesViewModel( + interactor: r.resolve(DatesInteractorProtocol.self)!, + connectivity: r.resolve(ConnectivityProtocol.self)!, + courseManager: r.resolve(CourseStructureManagerProtocol.self)!, + analytics: r.resolve(AppDatesAnalytics.self)!, + router: r.resolve(AppDatesRouter.self)! + ) + } + // MARK: Course container.register(CoursePersistenceProtocol.self) { r in CoursePersistence(container: r.resolve(DatabaseManager.self)!.getPersistentContainer()) diff --git a/OpenEdX/Data/DatabaseManager.swift b/OpenEdX/Data/DatabaseManager.swift index 567f580c7..d5a3e5a6a 100644 --- a/OpenEdX/Data/DatabaseManager.swift +++ b/OpenEdX/Data/DatabaseManager.swift @@ -2,7 +2,7 @@ // Persistence.swift // OpenEdX // -// Created by  Stepanok Ivan on 25.07.2023. +// Created by Stepanok Ivan on 25.07.2023. // import Foundation @@ -13,6 +13,7 @@ import Dashboard import Course import Downloads import Profile +import AppDates final class DatabaseManager: CoreDataHandlerProtocol { @@ -24,6 +25,7 @@ final class DatabaseManager: CoreDataHandlerProtocol { Bundle(for: DashboardBundle.self), Bundle(for: CourseBundle.self), Bundle(for: ProfileBundle.self), + Bundle(for: AppDatesBundle.self), Bundle(for: DownloadsBundle.self) ] diff --git a/OpenEdX/Data/DatesPersistence.swift b/OpenEdX/Data/DatesPersistence.swift new file mode 100644 index 000000000..8bceb226a --- /dev/null +++ b/OpenEdX/Data/DatesPersistence.swift @@ -0,0 +1,90 @@ +// +// DatesPersistence.swift +// OpenEdX +// +// Created by Ivan Stepanok on 15.02.2025. +// + +import Foundation +import Core +import AppDates +@preconcurrency import CoreData +import OEXFoundation + +public final class DatesPersistence: DatesPersistenceProtocol { + + private let container: NSPersistentContainer + + public init(container: NSPersistentContainer) { + self.container = container + } + + public func loadCourseDates(limit: Int? = nil, offset: Int? = nil) async throws -> [CourseDate] { + return try await container.performBackgroundTask { context in + let fetchRequest: NSFetchRequest = CDDate.fetchRequest() + + // Sort by index to ensure consistent order + fetchRequest.sortDescriptors = [NSSortDescriptor(key: "index", ascending: true)] + + // Apply limit and offset if provided + if let limit = limit { + fetchRequest.fetchLimit = limit + } + if let offset = offset { + fetchRequest.fetchOffset = offset + } + + let cdDates = try context.fetch(fetchRequest) + let result = cdDates.map { cd -> CourseDate in + let storedIndex = cd.value(forKey: "index") as? NSNumber + return CourseDate( + date: cd.date ?? Date(), + title: cd.title ?? "", + courseName: cd.courseName ?? "", + courseId: cd.courseId, + blockId: cd.blockId, + hasAccess: cd.hasAccess, + order: storedIndex?.intValue + ) + } + return result + } + } + + public func saveCourseDates(dates: [CourseDate], startIndex: Int) async { + await container.performBackgroundTask { context in + context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump + for (index, date) in dates.enumerated() { + let newItem = CDDate(context: context) + let resolvedIndex = date.order ?? (startIndex + index) + newItem.index = Int64(resolvedIndex) + newItem.date = date.date + newItem.title = date.title + newItem.courseName = date.courseName + newItem.courseId = date.courseId + newItem.blockId = date.blockId + newItem.hasAccess = date.hasAccess + } + + do { + try context.save() + } catch { + debugLog(error) + } + } + } + + public func clearAllCourseDates() async { + await container.performBackgroundTask { context in + let fetchRequest: NSFetchRequest = CDDate.fetchRequest() + let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + + do { + try context.execute(batchDeleteRequest) + try context.save() + } catch { + debugLog(error) + } + } + } +} diff --git a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift index 4a6e29426..b36b227d0 100644 --- a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift +++ b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift @@ -17,6 +17,7 @@ import WhatsNew import Downloads import Swinject import OEXFoundation +import AppDates // swiftlint:disable type_body_length file_length class AnalyticsManager: AuthorizationAnalytics, @@ -28,6 +29,7 @@ class AnalyticsManager: AuthorizationAnalytics, DiscussionAnalytics, CoreAnalytics, WhatsNewAnalytics, + AppDatesAnalytics, DownloadsAnalytics, @unchecked Sendable { @@ -165,6 +167,10 @@ class AnalyticsManager: AuthorizationAnalytics, trackScreenEvent(.mainProfileTabClicked, biValue: .mainProfileTabClicked) } + public func mainDatesScreenViewed() { + trackScreenEvent(.mainDashboardDatesTabClicked, biValue: .mainDashboardDatesTabClicked) + } + public func mainCoursesClicked() { trackScreenEvent(.mainDashboardCoursesClicked, biValue: .mainDashboardCoursesClicked) } @@ -1257,7 +1263,25 @@ class AnalyticsManager: AuthorizationAnalytics, logEvent(.whatnewClose, parameters: parameters) } - // MARK: - DownloadsAnalytics + // MARK: - AppDates Analytics + public func datesCourseClicked(courseId: String, courseName: String) { + let parameters = [ + EventParamKey.courseID: courseId, + EventParamKey.courseName: courseName, + EventParamKey.name: EventBIValue.datesCourseClicked.rawValue + ] + logEvent(.datesCourseClicked, parameters: parameters) + } + + public func datesSettingsClicked() { + trackEvent(.datesSettingsClicked, biValue: .datesSettingsClicked) + } + + public func datesRefreshPulled() { + trackEvent(.datesRefreshPulled, biValue: .datesRefreshPulled) + } + + // MARK: - DownloadsAnalytics public func downloadCourseClicked(courseId: String, courseName: String) { let parameters: [String: Any] = [ EventParamKey.courseID: courseId, diff --git a/OpenEdX/Managers/AnalyticsManager/MainScreenAnalytics.swift b/OpenEdX/Managers/AnalyticsManager/MainScreenAnalytics.swift index ffb66d691..566b6dbc2 100644 --- a/OpenEdX/Managers/AnalyticsManager/MainScreenAnalytics.swift +++ b/OpenEdX/Managers/AnalyticsManager/MainScreenAnalytics.swift @@ -15,6 +15,7 @@ public protocol MainScreenAnalytics: Sendable { func mainProfileTabClicked() func mainProgramsTabClicked() func mainCoursesClicked() + func mainDatesScreenViewed() func mainProgramsClicked() func notificationPermissionStatus(status: String) } @@ -26,6 +27,7 @@ final public class MainScreenAnalyticsMock: MainScreenAnalytics { public func mainDownloadsTabClicked() {} public func mainProfileTabClicked() {} public func mainProgramsTabClicked() {} + public func mainDatesScreenViewed() {} public func mainProgramsClicked() {} public func mainCoursesClicked() {} public func notificationPermissionStatus(status: String) {} diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index aac17b36d..d334f34e0 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -19,6 +19,7 @@ import Downloads import Profile import WhatsNew import Combine +import AppDates // swiftlint:disable type_body_length file_length public class Router: AuthorizationRouter, @@ -29,7 +30,8 @@ public class Router: AuthorizationRouter, DashboardRouter, CourseRouter, DiscussionRouter, - BackNavigationProtocol { + BackNavigationProtocol, + AppDatesRouter { public var container: Container diff --git a/OpenEdX/View/MainScreenView.swift b/OpenEdX/View/MainScreenView.swift index 786719f37..92e33ec23 100644 --- a/OpenEdX/View/MainScreenView.swift +++ b/OpenEdX/View/MainScreenView.swift @@ -10,6 +10,7 @@ import Discovery import Core import Swinject import Dashboard +import AppDates import Downloads import Profile import WhatsNew @@ -47,7 +48,7 @@ struct MainScreenView: View { viewModel: Container.shared.resolve(ListDashboardViewModel.self)!, router: Container.shared.resolve(DashboardRouter.self)! ) - + registerBanner } .tabItem { @@ -128,6 +129,22 @@ struct MainScreenView: View { .accessibilityIdentifier("discovery_tabitem") } + if viewModel.config.experimentalFeatures.appLevelDatesEnabled { + VStack { + DatesView(viewModel: Container.shared.resolve(DatesViewModel.self)!) + } + .tabItem { + if viewModel.selection == .dates { + CoreAssets.datesActive.swiftUIImage + } else { + CoreAssets.datesInactive.swiftUIImage + } + Text(AppDatesLocalization.Dates.title) + } + .tag(MainTab.dates) + .accessibilityIdentifier("dates_tabitem") + } + if viewModel.config.experimentalFeatures.appLevelDownloadsEnabled { AppDownloadsView(viewModel: Container.shared.resolve(AppDownloadsViewModel.self)!) .tabItem { @@ -158,8 +175,16 @@ struct MainScreenView: View { .tag(MainTab.profile) .accessibilityIdentifier("profile_tabitem") } - .navigationBarHidden(viewModel.selection == .dashboard || viewModel.selection == .downloads) - .navigationBarBackButtonHidden(viewModel.selection == .dashboard || viewModel.selection == .downloads) + .navigationBarHidden( + viewModel.selection == .dashboard + || viewModel.selection == .dates + || viewModel.selection == .downloads + ) + .navigationBarBackButtonHidden( + viewModel.selection == .dashboard + || viewModel.selection == .dates + || viewModel.selection == .downloads + ) .navigationTitle(titleBar()) .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: { @@ -202,6 +227,8 @@ struct MainScreenView: View { viewModel.trackMainDashboardLearnTabClicked() case .programs: viewModel.trackMainProgramsTabClicked() + case .dates: + viewModel.trackMainDatesScreenClicked() case .profile: viewModel.trackMainProfileTabClicked() case .downloads: @@ -220,6 +247,7 @@ struct MainScreenView: View { } } .accentColor(Theme.Colors.accentXColor) + if updateAvailable { UpdateNotificationView(config: viewModel.config) } @@ -251,6 +279,8 @@ struct MainScreenView: View { : DashboardLocalization.Learn.title case .programs: return CoreLocalization.Mainscreen.programs + case .dates: + return AppDatesLocalization.Dates.title case .downloads: return DownloadsLocalization.Downloads.title case .profile: diff --git a/OpenEdX/View/MainScreenViewModel.swift b/OpenEdX/View/MainScreenViewModel.swift index 8d0e956b8..67f24f528 100644 --- a/OpenEdX/View/MainScreenViewModel.swift +++ b/OpenEdX/View/MainScreenViewModel.swift @@ -19,6 +19,7 @@ public enum MainTab { case discovery case dashboard case programs + case dates case downloads case profile } @@ -111,6 +112,9 @@ final class MainScreenViewModel: ObservableObject { func trackMainProfileTabClicked() { analytics.mainProfileTabClicked() } + func trackMainDatesScreenClicked() { + analytics.mainDatesScreenViewed() + } @MainActor func showDownloadFailed(downloads: [DownloadDataTask]) async { diff --git a/Podfile b/Podfile index cd8c0854a..4bdfad284 100644 --- a/Podfile +++ b/Podfile @@ -18,7 +18,7 @@ abstract_target "App" do workspace './Core/Core.xcodeproj' target 'CoreTests' do - pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :tag => '4.2.0' + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :branch => 'master' end end @@ -27,7 +27,7 @@ abstract_target "App" do workspace './Authorization/Authorization.xcodeproj' target 'AuthorizationTests' do - pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :tag => '4.2.0' + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :branch => 'master' end end @@ -36,7 +36,7 @@ abstract_target "App" do workspace './Discovery/Discovery.xcodeproj' target 'DiscoveryUnitTests' do - pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :tag => '4.2.0' + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :branch => 'master' end end @@ -45,7 +45,7 @@ abstract_target "App" do workspace './WhatsNew/WhatsNew.xcodeproj' target 'WhatsNewTests' do - pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :tag => '4.2.0' + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :branch => 'master' end end @@ -54,7 +54,7 @@ abstract_target "App" do workspace './Dashboard/Dashboard.xcodeproj' target 'DashboardTests' do - pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :tag => '4.2.0' + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :branch => 'master' end end @@ -63,7 +63,7 @@ abstract_target "App" do workspace './Downloads/Downloads.xcodeproj' target 'DownloadsTests' do - pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :tag => '4.2.0' + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :branch => 'master' end end @@ -72,7 +72,7 @@ abstract_target "App" do workspace './Profile/Profile.xcodeproj' target 'ProfileTests' do - pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :tag => '4.2.0' + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :branch => 'master' end end @@ -81,7 +81,16 @@ abstract_target "App" do workspace './Course/Course.xcodeproj' target 'CourseTests' do - pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :tag => '4.2.0' + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :branch => 'master' + end + end + + target "AppDates" do + project './AppDates/AppDates.xcodeproj' + workspace './AppDates/AppDates.xcodeproj' + + target 'AppDatesTests' do + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :branch => 'master' end end @@ -90,7 +99,7 @@ abstract_target "App" do workspace './Discussion/Discussion.xcodeproj' target 'DiscussionTests' do - pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :tag => '4.2.0' + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :branch => 'master' end end @@ -99,7 +108,7 @@ abstract_target "App" do workspace './Theme/Theme.xcodeproj' target 'ThemeTests' do - pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :tag => '4.2.0' + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :branch => 'master' end end diff --git a/Podfile.lock b/Podfile.lock index ab2424669..7adff25f0 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -10,7 +10,7 @@ PODS: DEPENDENCIES: - SwiftGen (~> 6.6) - SwiftLint (~> 0.57.0) - - SwiftyMocky (from `https://github.com/MakeAWishFoundation/SwiftyMocky.git`, tag `4.2.0`) + - SwiftyMocky (from `https://github.com/MakeAWishFoundation/SwiftyMocky.git`, branch `master`) SPEC REPOS: trunk: @@ -20,13 +20,13 @@ SPEC REPOS: EXTERNAL SOURCES: SwiftyMocky: + :branch: master :git: https://github.com/MakeAWishFoundation/SwiftyMocky.git - :tag: 4.2.0 CHECKOUT OPTIONS: SwiftyMocky: + :commit: 3672eea08c7098214ac54ee26c7a7e39ea01a2f1 :git: https://github.com/MakeAWishFoundation/SwiftyMocky.git - :tag: 4.2.0 SPEC CHECKSUMS: Sourcery: 6f5fe49b82b7e02e8c65560cbd52e1be67a1af2e @@ -34,6 +34,6 @@ SPEC CHECKSUMS: SwiftLint: eb47480d47c982481592c195c221d11013a679cc SwiftyMocky: c5e96e4ff76ec6dbf5a5941aeb039b5a546954a0 -PODFILE CHECKSUM: 2d71ad797d49fa32b47c3315b92159de82824103 +PODFILE CHECKSUM: e8bbf406ccbac3957d776aa8c54be841a7924630 COCOAPODS: 1.16.2 diff --git a/Profile/Profile.xcodeproj/project.pbxproj b/Profile/Profile.xcodeproj/project.pbxproj index 5fb306ab5..2112dc76f 100644 --- a/Profile/Profile.xcodeproj/project.pbxproj +++ b/Profile/Profile.xcodeproj/project.pbxproj @@ -671,14 +671,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Profile-ProfileTests/Pods-App-Profile-ProfileTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-Profile-ProfileTests/Pods-App-Profile-ProfileTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App-Profile-ProfileTests/Pods-App-Profile-ProfileTests-resources.sh\"\n"; diff --git a/Profile/ProfileTests/ProfileMock.generated.swift b/Profile/ProfileTests/ProfileMock.generated.swift index 19b27d056..9254c0b96 100644 --- a/Profile/ProfileTests/ProfileMock.generated.swift +++ b/Profile/ProfileTests/ProfileMock.generated.swift @@ -3487,10 +3487,24 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M return __value } + open func shiftDueDates(courseID: String) throws { + addInvocation(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + do { + _ = try methodReturnValue(.m_shiftDueDates__courseID_courseID(Parameter.value(`courseID`))).casted() as Void + } catch MockError.notStubed { + // do nothing + } catch { + throw error + } + } + fileprivate enum MethodType { case m_getCourseBlocks__courseID_courseID(Parameter) case m_getLoadedCourseBlocks__courseID_courseID(Parameter) + case m_shiftDueDates__courseID_courseID(Parameter) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -3503,6 +3517,11 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) + + case (.m_shiftDueDates__courseID_courseID(let lhsCourseid), .m_shiftDueDates__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) default: return .none } } @@ -3511,12 +3530,14 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M switch self { case let .m_getCourseBlocks__courseID_courseID(p0): return p0.intValue case let .m_getLoadedCourseBlocks__courseID_courseID(p0): return p0.intValue + case let .m_shiftDueDates__courseID_courseID(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_getCourseBlocks__courseID_courseID: return ".getCourseBlocks(courseID:)" case .m_getLoadedCourseBlocks__courseID_courseID: return ".getLoadedCourseBlocks(courseID:)" + case .m_shiftDueDates__courseID_courseID: return ".shiftDueDates(courseID:)" } } } @@ -3556,6 +3577,16 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M willProduce(stubber) return given } + public static func shiftDueDates(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func shiftDueDates(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_shiftDueDates__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Void).self) + willProduce(stubber) + return given + } } public struct Verify { @@ -3563,6 +3594,7 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocks__courseID_courseID(`courseID`))} public static func getLoadedCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`))} + public static func shiftDueDates(courseID: Parameter) -> Verify { return Verify(method: .m_shiftDueDates__courseID_courseID(`courseID`))} } public struct Perform { @@ -3575,6 +3607,9 @@ open class CourseStructureManagerProtocolMock: CourseStructureManagerProtocol, M public static func getLoadedCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), performs: perform) } + public static func shiftDueDates(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_shiftDueDates__courseID_courseID(`courseID`), performs: perform) + } } public func given(_ method: Given) { diff --git a/Theme/Theme.xcodeproj.xcworkspace/contents.xcworkspacedata b/Theme/Theme.xcodeproj.xcworkspace/contents.xcworkspacedata index e3c199e57..7857f10d1 100644 --- a/Theme/Theme.xcodeproj.xcworkspace/contents.xcworkspacedata +++ b/Theme/Theme.xcodeproj.xcworkspace/contents.xcworkspacedata @@ -37,6 +37,9 @@ + + diff --git a/Theme/Theme/Assets.xcassets/Colors/CourseDates/NextWeekTimelineColor.colorset/Contents.json b/Theme/Theme/Assets.xcassets/Colors/CourseDates/NextWeekTimelineColor.colorset/Contents.json index d7638b312..316be36a1 100644 --- a/Theme/Theme/Assets.xcassets/Colors/CourseDates/NextWeekTimelineColor.colorset/Contents.json +++ b/Theme/Theme/Assets.xcassets/Colors/CourseDates/NextWeekTimelineColor.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xBA", - "green" : "0xA4", - "red" : "0x96" + "blue" : "0xBB", + "green" : "0xA5", + "red" : "0x97" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.439", - "green" : "0.353", - "red" : "0.306" + "blue" : "0x70", + "green" : "0x5A", + "red" : "0x4E" } }, "idiom" : "universal" diff --git a/Theme/Theme/Assets.xcassets/Colors/CourseDates/ThisWeekTimelineColor.colorset/Contents.json b/Theme/Theme/Assets.xcassets/Colors/CourseDates/ThisWeekTimelineColor.colorset/Contents.json index abd91436e..cb93439ab 100644 --- a/Theme/Theme/Assets.xcassets/Colors/CourseDates/ThisWeekTimelineColor.colorset/Contents.json +++ b/Theme/Theme/Assets.xcassets/Colors/CourseDates/ThisWeekTimelineColor.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.392", - "green" : "0.286", - "red" : "0.239" + "blue" : "0x64", + "green" : "0x49", + "red" : "0x3D" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.624", - "green" : "0.533", - "red" : "0.475" + "blue" : "0xAE", + "green" : "0x9B", + "red" : "0x8E" } }, "idiom" : "universal" diff --git a/Theme/Theme/Assets.xcassets/Colors/CourseDates/TodayTimelineColor.colorset/Contents.json b/Theme/Theme/Assets.xcassets/Colors/CourseDates/TodayTimelineColor.colorset/Contents.json index 3e35599d4..dfcfa64fa 100644 --- a/Theme/Theme/Assets.xcassets/Colors/CourseDates/TodayTimelineColor.colorset/Contents.json +++ b/Theme/Theme/Assets.xcassets/Colors/CourseDates/TodayTimelineColor.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "1.000", - "green" : "0.667", - "red" : "0.259" + "blue" : "0xE9", + "green" : "0x9A", + "red" : "0x3A" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "1.000", - "green" : "0.584", - "red" : "0.000" + "blue" : "0xFF", + "green" : "0x95", + "red" : "0x00" } }, "idiom" : "universal" diff --git a/Theme/Theme/Assets.xcassets/Colors/CourseDates/UpcomingTimelineColor.colorset/Contents.json b/Theme/Theme/Assets.xcassets/Colors/CourseDates/UpcomingTimelineColor.colorset/Contents.json index 34275d32c..5244e71eb 100644 --- a/Theme/Theme/Assets.xcassets/Colors/CourseDates/UpcomingTimelineColor.colorset/Contents.json +++ b/Theme/Theme/Assets.xcassets/Colors/CourseDates/UpcomingTimelineColor.colorset/Contents.json @@ -5,8 +5,8 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xDF", - "green" : "0xD3", + "blue" : "0xE0", + "green" : "0xD4", "red" : "0xCC" } }, @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.275", - "green" : "0.200", - "red" : "0.153" + "blue" : "0x46", + "green" : "0x33", + "red" : "0x27" } }, "idiom" : "universal" diff --git a/Theme/Theme/Assets.xcassets/Colors/CourseDates/pastDueTimelineColor.colorset/Contents.json b/Theme/Theme/Assets.xcassets/Colors/CourseDates/pastDueTimelineColor.colorset/Contents.json index a72efa461..7de4c60aa 100644 --- a/Theme/Theme/Assets.xcassets/Colors/CourseDates/pastDueTimelineColor.colorset/Contents.json +++ b/Theme/Theme/Assets.xcassets/Colors/CourseDates/pastDueTimelineColor.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.302", - "green" : "0.788", - "red" : "1.000" + "blue" : "0x4D", + "green" : "0xC9", + "red" : "0xFF" } }, "idiom" : "universal" diff --git a/WhatsNew/WhatsNew.xcodeproj/project.pbxproj b/WhatsNew/WhatsNew.xcodeproj/project.pbxproj index d1456fe36..40695e978 100644 --- a/WhatsNew/WhatsNew.xcodeproj/project.pbxproj +++ b/WhatsNew/WhatsNew.xcodeproj/project.pbxproj @@ -418,14 +418,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests-resources.sh\"\n"; diff --git a/default_config/dev/config.yaml b/default_config/dev/config.yaml index 0d4e370e5..0aa3ae7b1 100644 --- a/default_config/dev/config.yaml +++ b/default_config/dev/config.yaml @@ -12,6 +12,8 @@ SSO_BUTTON_TITLE: EXPERIMENTAL_FEATURES: APP_LEVEL_DOWNLOADS: ENABLED: false + APP_LEVEL_DATES: + ENABLED: false UI_COMPONENTS: COURSE_UNIT_PROGRESS_ENABLED: false diff --git a/default_config/prod/config.yaml b/default_config/prod/config.yaml index 0edb91b80..343a388ad 100644 --- a/default_config/prod/config.yaml +++ b/default_config/prod/config.yaml @@ -8,6 +8,12 @@ OAUTH_CLIENT_ID: '' SSO_BUTTON_TITLE: ar: "الدخول عبر SSO" en: "Sign in with SSO" + +EXPERIMENTAL_FEATURES: + APP_LEVEL_DOWNLOADS: + ENABLED: false + APP_LEVEL_DATES: + ENABLED: false UI_COMPONENTS: diff --git a/default_config/stage/config.yaml b/default_config/stage/config.yaml index 0edb91b80..e73d50e52 100644 --- a/default_config/stage/config.yaml +++ b/default_config/stage/config.yaml @@ -9,6 +9,11 @@ SSO_BUTTON_TITLE: ar: "الدخول عبر SSO" en: "Sign in with SSO" +EXPERIMENTAL_FEATURES: + APP_LEVEL_DOWNLOADS: + ENABLED: false + APP_LEVEL_DATES: + ENABLED: false UI_COMPONENTS: COURSE_UNIT_PROGRESS_ENABLED: false diff --git a/generateAllMocks.sh b/generateAllMocks.sh index 3f847aae2..f532b0696 100755 --- a/generateAllMocks.sh +++ b/generateAllMocks.sh @@ -19,3 +19,5 @@ cd ../Profile ./../Pods/SwiftyMocky/bin/swiftymocky generate cd ../WhatsNew ./../Pods/SwiftyMocky/bin/swiftymocky generate +cd ../AppDates +./../Pods/SwiftyMocky/bin/swiftymocky generate