diff --git a/Authorization/Authorization.xcodeproj/project.pbxproj b/Authorization/Authorization.xcodeproj/project.pbxproj index fee37da51..b0c3653d5 100644 --- a/Authorization/Authorization.xcodeproj/project.pbxproj +++ b/Authorization/Authorization.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 022D04962976DA6500E0059B /* AuthorizationMock.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022D04952976DA6500E0059B /* AuthorizationMock.generated.swift */; }; 025F40E029D1E2FC0064C183 /* ResetPasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025F40DF29D1E2FC0064C183 /* ResetPasswordView.swift */; }; 025F40E229D360E20064C183 /* ResetPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025F40E129D360E20064C183 /* ResetPasswordViewModel.swift */; }; + 02E0618429DC2373006E9024 /* ResetPasswordViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E0618329DC2373006E9024 /* ResetPasswordViewModelTests.swift */; }; 02F3BFE5292533720051930C /* AuthorizationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F3BFE4292533720051930C /* AuthorizationRouter.swift */; }; 071009C728D1DA4F00344290 /* SignInViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 071009C628D1DA4F00344290 /* SignInViewModel.swift */; }; 07169458296D913400E3DED6 /* Authorization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0770DE3B28D0A319006D8A5D /* Authorization.framework */; platformFilter = ios; }; @@ -45,6 +46,7 @@ 022D04952976DA6500E0059B /* AuthorizationMock.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationMock.generated.swift; sourceTree = ""; }; 025F40DF29D1E2FC0064C183 /* ResetPasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPasswordView.swift; sourceTree = ""; }; 025F40E129D360E20064C183 /* ResetPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPasswordViewModel.swift; sourceTree = ""; }; + 02E0618329DC2373006E9024 /* ResetPasswordViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPasswordViewModelTests.swift; sourceTree = ""; }; 02ED50CC29A64B90008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02F3BFE4292533720051930C /* AuthorizationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationRouter.swift; sourceTree = ""; }; 071009C628D1DA4F00344290 /* SignInViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewModel.swift; sourceTree = ""; }; @@ -170,6 +172,7 @@ 07169484296EC3D700E3DED6 /* Login */ = { isa = PBXGroup; children = ( + 02E0618329DC2373006E9024 /* ResetPasswordViewModelTests.swift */, 07169463296D96DD00E3DED6 /* SignInViewModelTests.swift */, ); path = Login; @@ -445,6 +448,7 @@ 022D04962976DA6500E0059B /* AuthorizationMock.generated.swift in Sources */, 022D0482297442BA00E0059B /* SignUpViewModelTests.swift in Sources */, 07169464296D96DD00E3DED6 /* SignInViewModelTests.swift in Sources */, + 02E0618429DC2373006E9024 /* ResetPasswordViewModelTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift index 1a9a5b4c8..beb1dc616 100644 --- a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift +++ b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift @@ -502,6 +502,12 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { perform?() } + open func showForgotPasswordScreen() { + addInvocation(.m_showForgotPasswordScreen) + let perform = methodPerformValue(.m_showForgotPasswordScreen) as? () -> Void + perform?() + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -536,6 +542,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case m_showMainScreen case m_showLoginScreen case m_showRegisterScreen + case m_showForgotPasswordScreen case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) @@ -571,6 +578,8 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case (.m_showRegisterScreen, .m_showRegisterScreen): return .match + case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsOktapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsOktapped, let rhsType)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) @@ -616,6 +625,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case .m_showMainScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 + case .m_showForgotPasswordScreen: return 0 case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_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_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue @@ -632,6 +642,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case .m_showMainScreen: return ".showMainScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" + case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped: return ".presentAlert(alertTitle:alertMessage:action:image:onCloseTapped:okTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" @@ -662,6 +673,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} + public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(`alertTitle`, `alertMessage`, `action`, `image`, `onCloseTapped`, `okTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} @@ -696,6 +708,9 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { public static func showRegisterScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showRegisterScreen, performs: perform) } + public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showForgotPasswordScreen, performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: 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_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) } @@ -875,6 +890,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?() } + open func showForgotPasswordScreen() { + addInvocation(.m_showForgotPasswordScreen) + let perform = methodPerformValue(.m_showForgotPasswordScreen) as? () -> Void + perform?() + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -909,6 +930,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showMainScreen case m_showLoginScreen case m_showRegisterScreen + case m_showForgotPasswordScreen case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) @@ -944,6 +966,8 @@ open class BaseRouterMock: BaseRouter, Mock { case (.m_showRegisterScreen, .m_showRegisterScreen): return .match + case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsOktapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsOktapped, let rhsType)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) @@ -989,6 +1013,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 + case .m_showForgotPasswordScreen: return 0 case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_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_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue @@ -1005,6 +1030,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return ".showMainScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" + case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped: return ".presentAlert(alertTitle:alertMessage:action:image:onCloseTapped:okTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" @@ -1035,6 +1061,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} + public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(`alertTitle`, `alertMessage`, `action`, `image`, `onCloseTapped`, `okTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} @@ -1069,6 +1096,9 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showRegisterScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showRegisterScreen, performs: perform) } + public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showForgotPasswordScreen, performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: 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_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) } @@ -1201,10 +1231,10 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { } private var __p_isInternetAvaliable: (Bool)? - public var isWifi: Bool { - get { invocations.append(.p_isWifi_get); return __p_isWifi ?? givenGetterValue(.p_isWifi_get, "ConnectivityProtocolMock - stub value for isWifi was not defined") } + 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_isWifi: (Bool)? + 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") } @@ -1218,12 +1248,12 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate enum MethodType { case p_isInternetAvaliable_get - case p_isWifi_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_isWifi_get,.p_isWifi_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 } @@ -1232,14 +1262,14 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { func intValue() -> Int { switch self { case .p_isInternetAvaliable_get: return 0 - case .p_isWifi_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_isWifi_get: return "[get] .isWifi" + case .p_isMobileData_get: return "[get] .isMobileData" case .p_internetReachableSubject_get: return "[get] .internetReachableSubject" } } @@ -1256,8 +1286,8 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { 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 isWifi(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_isWifi_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) })) @@ -1269,7 +1299,7 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate var method: MethodType public static var isInternetAvaliable: Verify { return Verify(method: .p_isInternetAvaliable_get) } - public static var isWifi: Verify { return Verify(method: .p_isWifi_get) } + public static var isMobileData: Verify { return Verify(method: .p_isMobileData_get) } public static var internetReachableSubject: Verify { return Verify(method: .p_internetReachableSubject_get) } } diff --git a/Authorization/AuthorizationTests/Presentation/Login/ResetPasswordViewModelTests.swift b/Authorization/AuthorizationTests/Presentation/Login/ResetPasswordViewModelTests.swift new file mode 100644 index 000000000..75be4e24f --- /dev/null +++ b/Authorization/AuthorizationTests/Presentation/Login/ResetPasswordViewModelTests.swift @@ -0,0 +1,167 @@ +// +// ResetPasswordViewModelTests.swift +// AuthorizationTests +// +// Created by  Stepanok Ivan on 04.04.2023. +// + +import SwiftyMocky +import XCTest +@testable import Core +@testable import Authorization +import Alamofire +import SwiftUI + +final class ResetPasswordViewModelTests: XCTestCase { + + func testResetPasswordValidationEmailError() async throws { + let interactor = AuthInteractorProtocolMock() + let router = AuthorizationRouterMock() + let validator = Validator() + let viewModel = ResetPasswordViewModel(interactor: interactor, router: router, validator: validator) + + var isRecoveryPassword = true + let binding = Binding(get: { + return isRecoveryPassword + }, set: { value in + isRecoveryPassword = value + }) + + await viewModel.resetPassword(email: "e", isRecovered: binding) + + Verify(interactor, 0, .resetPassword(email: .any)) + + XCTAssertEqual(viewModel.errorMessage, AuthLocalization.Error.invalidEmailAddress) + XCTAssertEqual(viewModel.isShowProgress, false) + XCTAssertEqual(isRecoveryPassword, true) + } + + func testResetPasswordSuccess() async throws { + let interactor = AuthInteractorProtocolMock() + let router = AuthorizationRouterMock() + let validator = Validator() + let viewModel = ResetPasswordViewModel(interactor: interactor, router: router, validator: validator) + + var isRecoveryPassword = true + let binding = Binding(get: { + return isRecoveryPassword + }, set: { value in + isRecoveryPassword = value + }) + + let data = ResetPassword(success: true, responseText: "Success") + Given(interactor, .resetPassword(email: .any, willReturn: data)) + + await viewModel.resetPassword(email: "edxUser@edx.com", isRecovered: binding) + + Verify(interactor, 1, .resetPassword(email: .any)) + + XCTAssertFalse(isRecoveryPassword) + XCTAssertEqual(viewModel.errorMessage, nil) + XCTAssertEqual(viewModel.isShowProgress, false) + XCTAssertEqual(isRecoveryPassword, false) + } + + func testResetPasswordErrorValidation() async throws { + let interactor = AuthInteractorProtocolMock() + let router = AuthorizationRouterMock() + let validator = Validator() + let viewModel = ResetPasswordViewModel(interactor: interactor, router: router, validator: validator) + + let validationErrorMessage = "Some error" + let validationError = CustomValidationError(statusCode: 400, data: ["value": validationErrorMessage]) + let error = AFError.responseValidationFailed(reason: AFError.ResponseValidationFailureReason.customValidationFailed(error: validationError)) + + Given(interactor, .resetPassword(email: .any, willThrow: error)) + + var isRecoveryPassword = true + let binding = Binding(get: { + return isRecoveryPassword + }, set: { value in + isRecoveryPassword = value + }) + + await viewModel.resetPassword(email: "edxUser@edx.com", isRecovered: binding) + + Verify(interactor, 1, .resetPassword(email: .any)) + + XCTAssertEqual(viewModel.errorMessage, validationErrorMessage) + XCTAssertEqual(viewModel.isShowProgress, false) + XCTAssertEqual(isRecoveryPassword, true) + } + + func testResetPasswordErrorInvalidGrant() async throws { + let interactor = AuthInteractorProtocolMock() + let router = AuthorizationRouterMock() + let validator = Validator() + let viewModel = ResetPasswordViewModel(interactor: interactor, router: router, validator: validator) + + Given(interactor, .resetPassword(email: .any, willThrow: APIError.invalidGrant)) + + var isRecoveryPassword = true + let binding = Binding(get: { + return isRecoveryPassword + }, set: { value in + isRecoveryPassword = value + }) + + await viewModel.resetPassword(email: "edxUser@edx.com", isRecovered: binding) + + Verify(interactor, 1, .resetPassword(email: .any)) + + XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.invalidCredentials) + XCTAssertEqual(viewModel.isShowProgress, false) + XCTAssertEqual(isRecoveryPassword, true) + } + + func testResetPasswordErrorUnknown() async throws { + let interactor = AuthInteractorProtocolMock() + let router = AuthorizationRouterMock() + let validator = Validator() + let viewModel = ResetPasswordViewModel(interactor: interactor, router: router, validator: validator) + + Given(interactor, .resetPassword(email: .any, willThrow: NSError())) + + var isRecoveryPassword = true + let binding = Binding(get: { + return isRecoveryPassword + }, set: { value in + isRecoveryPassword = value + }) + + await viewModel.resetPassword(email: "edxUser@edx.com", isRecovered: binding) + + Verify(interactor, 1, .resetPassword(email: .any)) + + XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.unknownError) + XCTAssertEqual(viewModel.isShowProgress, false) + XCTAssertEqual(isRecoveryPassword, true) + } + + func testResetPasswordNoInternetError() async throws { + let interactor = AuthInteractorProtocolMock() + let router = AuthorizationRouterMock() + let validator = Validator() + let viewModel = ResetPasswordViewModel(interactor: interactor, router: router, validator: validator) + + let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) + + Given(interactor, .resetPassword(email: .any, willThrow: noInternetError)) + + var isRecoveryPassword = true + let binding = Binding(get: { + return isRecoveryPassword + }, set: { value in + isRecoveryPassword = value + }) + + await viewModel.resetPassword(email: "edxUser@edx.com", isRecovered: binding) + + Verify(interactor, 1, .resetPassword(email: .any)) + + XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.slowOrNoInternetConnection) + XCTAssertEqual(viewModel.isShowProgress, false) + XCTAssertEqual(isRecoveryPassword, true) + } + +} diff --git a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift index 0340ab2b3..01748fb87 100644 --- a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift +++ b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift @@ -143,155 +143,5 @@ final class SignInViewModelTests: XCTestCase { XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.slowOrNoInternetConnection) XCTAssertEqual(viewModel.isShowProgress, false) } - - func testResetPasswordValidationEmailError() async throws { - let interactor = AuthInteractorProtocolMock() - let router = AuthorizationRouterMock() - let validator = Validator() - let viewModel = SignInViewModel(interactor: interactor, router: router, validator: validator) - - var isRecoveryPassword = true - let binding = Binding(get: { - return isRecoveryPassword - }, set: { value in - isRecoveryPassword = value - }) - - await viewModel.resetPassword(email: "e", isRecoveryPassord: binding) - - Verify(interactor, 0, .resetPassword(email: .any)) - - XCTAssertEqual(viewModel.errorMessage, AuthLocalization.Error.invalidEmailAddress) - XCTAssertEqual(viewModel.isShowProgress, false) - XCTAssertEqual(isRecoveryPassword, true) - } - - func testResetPasswordSuccess() async throws { - let interactor = AuthInteractorProtocolMock() - let router = AuthorizationRouterMock() - let validator = Validator() - let viewModel = SignInViewModel(interactor: interactor, router: router, validator: validator) - - var isRecoveryPassword = true - let binding = Binding(get: { - return isRecoveryPassword - }, set: { value in - isRecoveryPassword = value - }) - - let data = ResetPassword(success: true, responseText: "Success") - Given(interactor, .resetPassword(email: .any, willReturn: data)) - - await viewModel.resetPassword(email: "edxUser@edx.com", isRecoveryPassord: binding) - - Verify(interactor, 1, .resetPassword(email: .any)) - - XCTAssertEqual(viewModel.alertMessage, data.responseText) - XCTAssertEqual(viewModel.errorMessage, nil) - XCTAssertEqual(viewModel.isShowProgress, false) - XCTAssertEqual(isRecoveryPassword, false) - } - - func testResetPasswordErrorValidation() async throws { - let interactor = AuthInteractorProtocolMock() - let router = AuthorizationRouterMock() - let validator = Validator() - let viewModel = SignInViewModel(interactor: interactor, router: router, validator: validator) - - let validationErrorMessage = "Some error" - let validationError = CustomValidationError(statusCode: 400, data: ["value": validationErrorMessage]) - let error = AFError.responseValidationFailed(reason: AFError.ResponseValidationFailureReason.customValidationFailed(error: validationError)) - - Given(interactor, .resetPassword(email: .any, willThrow: error)) - - var isRecoveryPassword = true - let binding = Binding(get: { - return isRecoveryPassword - }, set: { value in - isRecoveryPassword = value - }) - - await viewModel.resetPassword(email: "edxUser@edx.com", isRecoveryPassord: binding) - - Verify(interactor, 1, .resetPassword(email: .any)) - - XCTAssertEqual(viewModel.errorMessage, validationErrorMessage) - XCTAssertEqual(viewModel.isShowProgress, false) - XCTAssertEqual(isRecoveryPassword, true) - } - - func testResetPasswordErrorInvalidGrant() async throws { - let interactor = AuthInteractorProtocolMock() - let router = AuthorizationRouterMock() - let validator = Validator() - let viewModel = SignInViewModel(interactor: interactor, router: router, validator: validator) - - Given(interactor, .resetPassword(email: .any, willThrow: APIError.invalidGrant)) - - var isRecoveryPassword = true - let binding = Binding(get: { - return isRecoveryPassword - }, set: { value in - isRecoveryPassword = value - }) - - await viewModel.resetPassword(email: "edxUser@edx.com", isRecoveryPassord: binding) - - Verify(interactor, 1, .resetPassword(email: .any)) - - XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.invalidCredentials) - XCTAssertEqual(viewModel.isShowProgress, false) - XCTAssertEqual(isRecoveryPassword, true) - } - - func testResetPasswordErrorUnknown() async throws { - let interactor = AuthInteractorProtocolMock() - let router = AuthorizationRouterMock() - let validator = Validator() - let viewModel = SignInViewModel(interactor: interactor, router: router, validator: validator) - - Given(interactor, .resetPassword(email: .any, willThrow: NSError())) - - var isRecoveryPassword = true - let binding = Binding(get: { - return isRecoveryPassword - }, set: { value in - isRecoveryPassword = value - }) - - await viewModel.resetPassword(email: "edxUser@edx.com", isRecoveryPassord: binding) - - Verify(interactor, 1, .resetPassword(email: .any)) - - XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.unknownError) - XCTAssertEqual(viewModel.isShowProgress, false) - XCTAssertEqual(isRecoveryPassword, true) - } - - func testResetPasswordNoInternetError() async throws { - let interactor = AuthInteractorProtocolMock() - let router = AuthorizationRouterMock() - let validator = Validator() - let viewModel = SignInViewModel(interactor: interactor, router: router, validator: validator) - - let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) - - Given(interactor, .resetPassword(email: .any, willThrow: noInternetError)) - - var isRecoveryPassword = true - let binding = Binding(get: { - return isRecoveryPassword - }, set: { value in - isRecoveryPassword = value - }) - - await viewModel.resetPassword(email: "edxUser@edx.com", isRecoveryPassord: binding) - - Verify(interactor, 1, .resetPassword(email: .any)) - - XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.slowOrNoInternetConnection) - XCTAssertEqual(viewModel.isShowProgress, false) - XCTAssertEqual(isRecoveryPassword, true) - } } diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 4c75885dc..061f106a5 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -70,6 +70,7 @@ 02B2B594295C5C7A00914876 /* Thread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B2B593295C5C7A00914876 /* Thread.swift */; }; 02B3E3B32930198600A50475 /* AVPlayerViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3E3B22930198600A50475 /* AVPlayerViewControllerExtension.swift */; }; 02C2DC0829B63D6200F4445D /* WebViewHTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C2DC0729B63D6200F4445D /* WebViewHTML.swift */; }; + 02C917F029CDA99E00DBB8BD /* Data_Dashboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C917EF29CDA99E00DBB8BD /* Data_Dashboard.swift */; }; 02CF46C829546AA200A698EE /* NoCachedDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CF46C729546AA200A698EE /* NoCachedDataError.swift */; }; 02D800CC29348F460099CF16 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D800CB29348F460099CF16 /* ImagePicker.swift */; }; 02E225B0291D29EB0067769A /* UrlExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E225AF291D29EB0067769A /* UrlExtension.swift */; }; @@ -187,6 +188,7 @@ 02B2B593295C5C7A00914876 /* Thread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thread.swift; sourceTree = ""; }; 02B3E3B22930198600A50475 /* AVPlayerViewControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerViewControllerExtension.swift; sourceTree = ""; }; 02C2DC0729B63D6200F4445D /* WebViewHTML.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewHTML.swift; sourceTree = ""; }; + 02C917EF29CDA99E00DBB8BD /* Data_Dashboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data_Dashboard.swift; sourceTree = ""; }; 02CF46C729546AA200A698EE /* NoCachedDataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCachedDataError.swift; sourceTree = ""; }; 02D800CB29348F460099CF16 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; 02E225AF291D29EB0067769A /* UrlExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlExtension.swift; sourceTree = ""; }; @@ -388,6 +390,7 @@ 0727877628D23847002E9142 /* DataLayer.swift */, 0727878428D31657002E9142 /* Data_User.swift */, 0283347C28D4D3DE00C828FC /* Data_Discovery.swift */, + 02C917EF29CDA99E00DBB8BD /* Data_Dashboard.swift */, 027DB33428D8C8FE002B6862 /* Data_MyCourse.swift */, 021D924728DC860C00ACC565 /* Data_UserProfile.swift */, 0259104929C4A5B6004B5A55 /* UserSettings.swift */, @@ -773,6 +776,7 @@ 027BD3BE2909478B00392132 /* UIResponder+CurrentResponder.swift in Sources */, 070019AE28F701B200D5FC78 /* Certificate.swift in Sources */, 0770DE5F28D0B22C006D8A5D /* Strings.swift in Sources */, + 02C917F029CDA99E00DBB8BD /* Data_Dashboard.swift in Sources */, 024FCD0028EF1CD300232339 /* WebBrowser.swift in Sources */, 027BD3B52909475900392132 /* KeyboardStateObserver.swift in Sources */, 0283347D28D4D3DE00C828FC /* Data_Discovery.swift in Sources */, diff --git a/Core/Core/Configuration/Connectivity.swift b/Core/Core/Configuration/Connectivity.swift index 1be9f1bb9..7a04f494a 100644 --- a/Core/Core/Configuration/Connectivity.swift +++ b/Core/Core/Configuration/Connectivity.swift @@ -16,7 +16,7 @@ public enum InternetState { //sourcery: AutoMockable public protocol ConnectivityProtocol { var isInternetAvaliable: Bool { get } - var isWifi: Bool { get } + var isMobileData: Bool { get } var internetReachableSubject: CurrentValueSubject { get } } @@ -28,9 +28,9 @@ public class Connectivity: ConnectivityProtocol { networkManager?.isReachable ?? false } - public var isWifi: Bool { + public var isMobileData: Bool { if let networkManager { - return !networkManager.isReachableOnCellular && networkManager.isReachableOnEthernetOrWiFi + return !networkManager.isReachableOnCellular && networkManager.isReachableOnCellular } else { return false } diff --git a/Core/Core/Data/AppStorage.swift b/Core/Core/Data/AppStorage.swift index 364036456..7bd28811a 100644 --- a/Core/Core/Data/AppStorage.swift +++ b/Core/Core/Data/AppStorage.swift @@ -79,7 +79,12 @@ public class AppStorage { public var userSettings: UserSettings? { get { guard let userSettings = userDefaults.data(forKey: KEY_SETTINGS) else { - return nil + let defaultSettings = UserSettings(wifiOnly: false, downloadQuality: .auto) + let encoder = JSONEncoder() + if let encoded = try? encoder.encode(defaultSettings) { + userDefaults.set(encoded, forKey: KEY_SETTINGS) + } + return defaultSettings } return try? JSONDecoder().decode(UserSettings.self, from: userSettings) } diff --git a/Core/Core/Data/Model/Data_Dashboard.swift b/Core/Core/Data/Model/Data_Dashboard.swift new file mode 100644 index 000000000..86874824d --- /dev/null +++ b/Core/Core/Data/Model/Data_Dashboard.swift @@ -0,0 +1,96 @@ +// +// Data_Dashboard.swift +// Core +// +// Created by  Stepanok Ivan on 24.03.2023. +// + +import Foundation + +public extension DataLayer { + struct CourseEnrollments: Codable { + public let next: String? + public let previous: String? + public let count: Int + public let numPages: Int + public let currentPage: Int + public let start: Int + public let results: [Result] + + enum CodingKeys: String, CodingKey { + case next = "next" + case previous = "previous" + case count = "count" + case numPages = "num_pages" + case currentPage = "current_page" + case start = "start" + case results = "results" + } + + public init(next: String?, + previous: String?, + count: Int, + numPages: Int, + currentPage: Int, + start: Int, + results: [Result]) { + self.next = next + self.previous = previous + self.count = count + self.numPages = numPages + self.currentPage = currentPage + self.start = start + self.results = results + } + } + + // MARK: - Result + struct Result: Codable { + public let auditAccessExpires: String? + public let created: String + public let isActive: Bool + public let course: Course + public let certificate: Certificate + + enum CodingKeys: String, CodingKey { + case auditAccessExpires = "audit_access_expires" + case created = "created" + case isActive = "is_active" + case course = "course" + case certificate = "certificate" + } + + public init(auditAccessExpires: String?, created: String,// mode: Mode, + isActive: Bool, course: Course, certificate: Certificate) { + self.auditAccessExpires = auditAccessExpires + self.created = created + self.isActive = isActive + self.course = course + self.certificate = certificate + } + } +} + +public extension DataLayer.CourseEnrollments { + func domain(baseURL: String) -> [CourseItem] { + + return results.map { course in + let imageURL = baseURL + (course.course.media.courseImage?.url?.addingPercentEncoding( + withAllowedCharacters: .urlQueryAllowed) ?? "") + return CourseItem( + name: course.course.name, + org: course.course.org, + shortDescription: "", + imageURL: imageURL, + isActive: true, + courseStart: course.course.start != nil ? Date(iso8601: course.course.start!) : nil, + courseEnd: course.course.end != nil ? Date(iso8601: course.course.end!) : nil, + enrollmentStart: course.course.enrollmentStart != nil ? Date(iso8601: course.course.enrollmentStart!) : nil, + enrollmentEnd: course.course.enrollmentEnd != nil ? Date(iso8601: course.course.enrollmentEnd!) : nil, + courseID: course.course.id, + numPages: numPages, + coursesCount: count + ) + } + } +} diff --git a/Core/Core/Data/Model/Data_Discovery.swift b/Core/Core/Data/Model/Data_Discovery.swift index e6886e640..e44786bc9 100644 --- a/Core/Core/Data/Model/Data_Discovery.swift +++ b/Core/Core/Data/Model/Data_Discovery.swift @@ -43,7 +43,7 @@ public extension DataLayer { public extension DataLayer { // MARK: - Result struct Course: Codable { - public let blocksURL: String + public let blocksURL: String? public let effort: String? public let end: String? public let enrollmentStart: String? @@ -57,11 +57,11 @@ public extension DataLayer { public let start: String? public let startDisplay: String? public let startType: String? - public let pacing: Pacing - public let mobileAvailable: Bool - public let hidden: Bool - public let invitationOnly: Bool - public let courseID: String + public let pacing: Pacing? + public let mobileAvailable: Bool? + public let hidden: Bool? + public let invitationOnly: Bool? + public let courseID: String? enum CodingKeys: String, CodingKey { case blocksURL = "blocks_url" @@ -102,14 +102,13 @@ public extension DataLayer.DiscoveryResponce { let listReady = results.map({ CourseItem(name: $0.name, org: $0.org, shortDescription: $0.shortDescription ?? "", - imageURL: $0.media.image.small, + imageURL: $0.media.image?.small ?? "", isActive: nil, courseStart: Date(iso8601: $0.start ?? ""), courseEnd: Date(iso8601: $0.end ?? ""), enrollmentStart: Date(iso8601: $0.enrollmentStart ?? ""), enrollmentEnd: Date(iso8601: $0.enrollmentEnd ?? ""), - courseID: $0.courseID, - certificate: nil, + courseID: $0.courseID ?? "", numPages: pagination.numPages, coursesCount: pagination.count) }) diff --git a/Core/Core/Data/Model/Data_Media.swift b/Core/Core/Data/Model/Data_Media.swift index 983e88bcb..62cf646af 100644 --- a/Core/Core/Data/Model/Data_Media.swift +++ b/Core/Core/Data/Model/Data_Media.swift @@ -8,12 +8,22 @@ import Foundation public extension DataLayer { + + // MARK: - CourseMedia + struct CourseMedia: Decodable { + public let image: DataLayer.Image + + public init(image: DataLayer.Image) { + self.image = image + } + } + // MARK: - Media struct Media: Codable { - public let bannerImage: DataLayer.BannerImage - public let courseImage: DataLayer.CourseUrl - public let courseVideo: DataLayer.CourseUrl - public let image: DataLayer.Image + public let bannerImage: DataLayer.BannerImage? + public let courseImage: DataLayer.CourseUrl? + public let courseVideo: DataLayer.CourseUrl? + public let image: DataLayer.Image? enum CodingKeys: String, CodingKey { case bannerImage = "banner_image" @@ -61,5 +71,11 @@ public extension DataLayer { case small case large } + + public init(raw: String, small: String, large: String) { + self.raw = raw + self.small = small + self.large = large + } } } diff --git a/Core/Core/Data/Model/Data_MyCourse.swift b/Core/Core/Data/Model/Data_MyCourse.swift index 07eb3d41c..f11e27bd7 100644 --- a/Core/Core/Data/Model/Data_MyCourse.swift +++ b/Core/Core/Data/Model/Data_MyCourse.swift @@ -32,6 +32,10 @@ public extension DataLayer { // MARK: - Certificate struct Certificate: Codable { public let url: String? + + public init(url: String?) { + self.url = url + } } // MARK: - Course @@ -148,7 +152,6 @@ public extension DataLayer.MyCourse { enrollmentStart: nil, enrollmentEnd: nil, courseID: course.id, - certificate: certificate?.domain, numPages: 1, coursesCount: 0 ) diff --git a/Core/Core/Domain/Model/Certificate.swift b/Core/Core/Domain/Model/Certificate.swift index 8b90bb0ef..e85a51a31 100644 --- a/Core/Core/Domain/Model/Certificate.swift +++ b/Core/Core/Domain/Model/Certificate.swift @@ -8,9 +8,9 @@ import Foundation public struct Certificate: Codable, Hashable { - public let url: String + public let url: String? - public init(url: String) { + public init(url: String?) { self.url = url } } diff --git a/Core/Core/Domain/Model/CourseBlockModel.swift b/Core/Core/Domain/Model/CourseBlockModel.swift index 5d9542472..4a8049f73 100644 --- a/Core/Core/Domain/Model/CourseBlockModel.swift +++ b/Core/Core/Domain/Model/CourseBlockModel.swift @@ -8,6 +8,18 @@ import Foundation public struct CourseStructure: Equatable { + + public let id: String + public let graded: Bool + public let completion: Double + public let viewYouTubeUrl: String + public let encodedVideo: String + public let displayName: String + public let topicID: String? + public let childs: [CourseChapter] + public let media: DataLayer.CourseMedia + public let certificate: Certificate? + public init(id: String, graded: Bool, completion: Double, @@ -15,7 +27,9 @@ public struct CourseStructure: Equatable { encodedVideo: String, displayName: String, topicID: String? = nil, - childs: [CourseChapter]) { + childs: [CourseChapter], + media: DataLayer.CourseMedia, + certificate: Certificate?) { self.id = id self.graded = graded self.completion = completion @@ -24,20 +38,14 @@ public struct CourseStructure: Equatable { self.displayName = displayName self.topicID = topicID self.childs = childs + self.media = media + self.certificate = certificate } public static func == (lhs: CourseStructure, rhs: CourseStructure) -> Bool { return lhs.id == rhs.id } - public let id: String - public let graded: Bool - public let completion: Double - public let viewYouTubeUrl: String - public let encodedVideo: String - public let displayName: String - public let topicID: String? - public let childs: [CourseChapter] } public struct CourseChapter { diff --git a/Core/Core/Domain/Model/CourseItem.swift b/Core/Core/Domain/Model/CourseItem.swift index a208da50b..9229417f1 100644 --- a/Core/Core/Domain/Model/CourseItem.swift +++ b/Core/Core/Domain/Model/CourseItem.swift @@ -18,7 +18,6 @@ public struct CourseItem: Hashable { public let enrollmentStart: Date? public let enrollmentEnd: Date? public let courseID: String - public let certificate: Certificate? public let numPages: Int public let coursesCount: Int @@ -32,7 +31,6 @@ public struct CourseItem: Hashable { enrollmentStart: Date?, enrollmentEnd: Date?, courseID: String, - certificate: Certificate?, numPages: Int, coursesCount: Int) { self.name = name @@ -45,7 +43,6 @@ public struct CourseItem: Hashable { self.enrollmentStart = enrollmentStart self.enrollmentEnd = enrollmentEnd self.courseID = courseID - self.certificate = certificate self.numPages = numPages self.coursesCount = coursesCount } diff --git a/Core/Core/Network/DownloadManager.swift b/Core/Core/Network/DownloadManager.swift index 8cfc277b1..0392c13f5 100644 --- a/Core/Core/Network/DownloadManager.swift +++ b/Core/Core/Network/DownloadManager.swift @@ -68,7 +68,7 @@ public class DownloadManager: DownloadManagerProtocol { } public func addToDownloadQueue(blocks: [CourseBlock]) throws { - if userCanDownloadByWifiOnly() { + if userCanDownload() { persistence.addToDownloadQueue(blocks: blocks) try newDownload() } else { @@ -77,7 +77,7 @@ public class DownloadManager: DownloadManagerProtocol { } private func newDownload() throws { - if userCanDownloadByWifiOnly() { + if userCanDownload() { guard let download = persistence.getBlocksForDownloading().first else { isDownloadingInProgress = false return @@ -89,8 +89,16 @@ public class DownloadManager: DownloadManagerProtocol { } } - private func userCanDownloadByWifiOnly() -> Bool { - return appStorage.userSettings?.wifiOnly ?? false && connectivity.isWifi + private func userCanDownload() -> Bool { + if appStorage.userSettings?.wifiOnly ?? true { + if !connectivity.isMobileData { + return true + } else { + return false + } + } else { + return true + } } public func getAllDownloads() -> [DownloadData] { diff --git a/Core/Core/View/Base/CourseCellView.swift b/Core/Core/View/Base/CourseCellView.swift index b6214b403..1388d1489 100644 --- a/Core/Core/View/Base/CourseCellView.swift +++ b/Core/Core/View/Base/CourseCellView.swift @@ -124,7 +124,6 @@ struct CourseCellView_Previews: PreviewProvider { enrollmentStart: nil, enrollmentEnd: nil, courseID: "1", - certificate: nil, numPages: 1, coursesCount: 10) diff --git a/Course/Course/Data/CourseRepository.swift b/Course/Course/Data/CourseRepository.swift index 21f4737b5..0412e0d8c 100644 --- a/Course/Course/Data/CourseRepository.swift +++ b/Course/Course/Data/CourseRepository.swift @@ -10,11 +10,9 @@ import Core public protocol CourseRepositoryProtocol { func getCourseDetails(courseID: String) async throws -> CourseDetails - func getEnrollments() async throws -> [CourseItem] func getCourseBlocks(courseID: String) async throws -> CourseStructure func getCourseDetailsOffline(courseID: String) async throws -> CourseDetails - func getEnrollmentsOffline() async throws -> [CourseItem] - func getCourseBlocksOffline() throws -> CourseStructure + func getCourseBlocksOffline(courseID: String) throws -> CourseStructure func enrollToCourse(courseID: String) async throws -> Bool func blockCompletionRequest(courseID: String, blockID: String) async throws func getHandouts(courseID: String) async throws -> String? @@ -50,35 +48,19 @@ public class CourseRepository: CourseRepositoryProtocol { public func getCourseDetailsOffline(courseID: String) async throws -> CourseDetails { return try persistence.loadCourseDetails(courseID: courseID) } - - public func getEnrollments() async throws -> [CourseItem] { - let myCoursesResponse = try await api.requestData( - CourseDetailsEndpoint.getEnrollments(username: appStorage.user?.username ?? "") - ) - .mapResponse([DataLayer.MyCourse].self) - .map({ course in - course.domain(baseURL: config.baseURL.absoluteString) - }) - persistence.saveEnrollments(items: myCoursesResponse) - return myCoursesResponse - } - - public func getEnrollmentsOffline() async throws -> [CourseItem] { - return try persistence.loadEnrollments() - } - + public func getCourseBlocks(courseID: String) async throws -> CourseStructure { - let courseBlocks = try await api.requestData( + let structure = try await api.requestData( CourseDetailsEndpoint.getCourseBlocks(courseID: courseID, userName: appStorage.user?.username ?? "") - ).mapResponse(BECourseDetailBlocks.self) - persistence.saveCourseStructure(structure: Array(courseBlocks.dict.values)) - let parsedStructure = parseCourseStructure(blocks: Array(courseBlocks.dict.values)) + ).mapResponse(DataLayer.CourseStructure.self) + persistence.saveCourseStructure(structure: structure) + let parsedStructure = parseCourseStructure(structure: structure) return parsedStructure } - public func getCourseBlocksOffline() throws -> CourseStructure { - let localData = try persistence.loadCourseStructure() - return parseCourseStructure(blocks: localData) + public func getCourseBlocksOffline(courseID: String) throws -> CourseStructure { + let localData = try persistence.loadCourseStructure(courseID: courseID) + return parseCourseStructure(structure: localData) } public func enrollToCourse(courseID: String) async throws -> Bool { @@ -109,7 +91,8 @@ public class CourseRepository: CourseRepositoryProtocol { .mapResponse(DataLayer.CourseUpdates.self).map { $0.domain } } - private func parseCourseStructure(blocks: [BECourseDetailIncoming]) -> CourseStructure { + private func parseCourseStructure(structure: DataLayer.CourseStructure) -> CourseStructure { + let blocks = Array(structure.dict.values) let course = blocks.first(where: {$0.type == BlockType.course.rawValue })! let descendants = course.descendants ?? [] var childs: [CourseChapter] = [] @@ -125,10 +108,12 @@ public class CourseRepository: CourseRepositoryProtocol { encodedVideo: course.userViewData?.encodedVideo?.fallback?.url ?? "", displayName: course.displayName, topicID: course.userViewData?.topicID, - childs: childs) + childs: childs, + media: structure.media, + certificate: structure.certificate?.domain) } - private func parseChapters(id: String, blocks: [BECourseDetailIncoming]) -> CourseChapter { + private func parseChapters(id: String, blocks: [DataLayer.CourseBlock]) -> CourseChapter { let chapter = blocks.first(where: {$0.id == id })! let descendants = chapter.descendants ?? [] var childs: [CourseSequential] = [] @@ -144,7 +129,7 @@ public class CourseRepository: CourseRepositoryProtocol { } - private func parseSequential(id: String, blocks: [BECourseDetailIncoming]) -> CourseSequential { + private func parseSequential(id: String, blocks: [DataLayer.CourseBlock]) -> CourseSequential { let sequential = blocks.first(where: {$0.id == id })! let descendants = sequential.descendants ?? [] var childs: [CourseVertical] = [] @@ -160,7 +145,7 @@ public class CourseRepository: CourseRepositoryProtocol { childs: childs) } - private func parseVerticals(id: String, blocks: [BECourseDetailIncoming]) -> CourseVertical { + private func parseVerticals(id: String, blocks: [DataLayer.CourseBlock]) -> CourseVertical { let sequential = blocks.first(where: {$0.id == id })! let descendants = sequential.descendants ?? [] var childs: [CourseBlock] = [] @@ -176,7 +161,7 @@ public class CourseRepository: CourseRepositoryProtocol { childs: childs) } - private func parseBlock(id: String, blocks: [BECourseDetailIncoming]) -> CourseBlock { + private func parseBlock(id: String, blocks: [DataLayer.CourseBlock]) -> CourseBlock { let block = blocks.first(where: {$0.id == id })! return CourseBlock(blockId: block.blockId, id: block.id, @@ -215,20 +200,17 @@ class CourseRepositoryMock: CourseRepositoryProtocol { courseEnd: Date(iso8601: "2022-05-26T12:13:14Z"), enrollmentStart: nil, enrollmentEnd: nil, + isEnrolled: false, overviewHTML: "Course description

Lorem ipsum", courseBannerURL: "courseBannerURL" ) } - func getEnrollmentsOffline() async throws -> [Core.CourseItem] { - return [] - } - - func getCourseBlocksOffline() throws -> CourseStructure { + func getCourseBlocksOffline(courseID: String) throws -> CourseStructure { let decoder = JSONDecoder() let jsonData = Data(courseStructureJson.utf8) - let courseBlocks = try decoder.decode(BECourseDetailBlocks.self, from: jsonData)// else { throw NoCachedDataError() } - return parseCourseStructure(blocks: Array(courseBlocks.dict.values)) + let courseBlocks = try decoder.decode(DataLayer.CourseStructure.self, from: jsonData) + return parseCourseStructure(courseBlocks: courseBlocks) } public func getCourseDetails(courseID: String) async throws -> CourseDetails { @@ -241,19 +223,18 @@ class CourseRepositoryMock: CourseRepositoryProtocol { courseEnd: Date(iso8601: "2022-05-26T12:13:14Z"), enrollmentStart: nil, enrollmentEnd: nil, + isEnrolled: false, overviewHTML: "Course description

Lorem ipsum", courseBannerURL: "courseBannerURL" ) } - - func getEnrollments() async throws -> [CourseItem] { return [] } - + public func getCourseBlocks(courseID: String) async throws -> CourseStructure { do { let decoder = JSONDecoder() let jsonData = Data(courseStructureJson.utf8) - let courseBlocks = try decoder.decode(BECourseDetailBlocks.self, from: jsonData) - return parseCourseStructure(blocks: Array(courseBlocks.dict.values)) + let courseBlocks = try decoder.decode(DataLayer.CourseStructure.self, from: jsonData) + return parseCourseStructure(courseBlocks: courseBlocks) } catch { throw error } @@ -267,8 +248,8 @@ class CourseRepositoryMock: CourseRepositoryProtocol { } - private func parseCourseStructure(blocks: [BECourseDetailIncoming]) -> CourseStructure { - + private func parseCourseStructure(courseBlocks: DataLayer.CourseStructure) -> CourseStructure { + let blocks = Array(courseBlocks.dict.values) let course = blocks.first(where: {$0.type == BlockType.course.rawValue })! let descendants = course.descendants ?? [] var childs: [CourseChapter] = [] @@ -284,11 +265,12 @@ class CourseRepositoryMock: CourseRepositoryProtocol { encodedVideo: course.userViewData?.encodedVideo?.fallback?.url ?? "", displayName: course.displayName, topicID: course.userViewData?.topicID, - childs: childs) + childs: childs, + media: courseBlocks.media, + certificate: courseBlocks.certificate?.domain) } - private func parseChapters(id: String, blocks: [BECourseDetailIncoming]) -> CourseChapter { - + private func parseChapters(id: String, blocks: [DataLayer.CourseBlock]) -> CourseChapter { let chapter = blocks.first(where: {$0.id == id })! let descendants = chapter.descendants ?? [] var childs: [CourseSequential] = [] @@ -301,11 +283,9 @@ class CourseRepositoryMock: CourseRepositoryProtocol { displayName: chapter.displayName, type: BlockType(rawValue: chapter.type) ?? .unknown, childs: childs) - } - private func parseSequential(id: String, blocks: [BECourseDetailIncoming]) -> CourseSequential { - + private func parseSequential(id: String, blocks: [DataLayer.CourseBlock]) -> CourseSequential { let sequential = blocks.first(where: {$0.id == id })! let descendants = sequential.descendants ?? [] var childs: [CourseVertical] = [] @@ -321,8 +301,7 @@ class CourseRepositoryMock: CourseRepositoryProtocol { childs: childs) } - private func parseVerticals(id: String, blocks: [BECourseDetailIncoming]) -> CourseVertical { - + private func parseVerticals(id: String, blocks: [DataLayer.CourseBlock]) -> CourseVertical { let sequential = blocks.first(where: {$0.id == id })! let descendants = sequential.descendants ?? [] var childs: [CourseBlock] = [] @@ -338,8 +317,7 @@ class CourseRepositoryMock: CourseRepositoryProtocol { childs: childs) } - private func parseBlock(id: String, blocks: [BECourseDetailIncoming]) -> CourseBlock { - + private func parseBlock(id: String, blocks: [DataLayer.CourseBlock]) -> CourseBlock { let block = blocks.first(where: {$0.id == id })! return CourseBlock(blockId: block.blockId, id: block.id, diff --git a/Course/Course/Data/Model/Data_CourseDetailsResponse.swift b/Course/Course/Data/Model/Data_CourseDetailsResponse.swift index a22e3b13d..e1b3a3bb7 100644 --- a/Course/Course/Data/Model/Data_CourseDetailsResponse.swift +++ b/Course/Course/Data/Model/Data_CourseDetailsResponse.swift @@ -16,6 +16,7 @@ public extension DataLayer { public let end: String? public let enrollmentStart: String? public let enrollmentEnd: String? + public let isEnrolled: Bool public let id: String public let media: Media public let name: String @@ -38,6 +39,7 @@ public extension DataLayer { case end case enrollmentStart = "enrollment_start" case enrollmentEnd = "enrollment_end" + case isEnrolled = "is_enrolled" case id case media case name @@ -59,7 +61,7 @@ public extension DataLayer { public extension DataLayer.CourseDetailsResponse { func domain(baseURL: String) -> CourseDetails { - let imageURL = baseURL + (media.courseImage.url?.addingPercentEncoding( + let imageURL = baseURL + (media.courseImage?.url?.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed) ?? "") return CourseDetails( courseID: id, @@ -70,6 +72,7 @@ public extension DataLayer.CourseDetailsResponse { courseEnd: end != nil ? Date(iso8601: end!) : nil, enrollmentStart: enrollmentStart != nil ? Date(iso8601: enrollmentStart!) : nil, enrollmentEnd: enrollmentEnd != nil ? Date(iso8601: enrollmentEnd!) : nil, + isEnrolled: isEnrolled, overviewHTML: overview, courseBannerURL: imageURL) } diff --git a/Course/Course/Data/Model/Data_CourseOutlineResponse.swift b/Course/Course/Data/Model/Data_CourseOutlineResponse.swift index e0b3b6b19..d9bb18f27 100644 --- a/Course/Course/Data/Model/Data_CourseOutlineResponse.swift +++ b/Course/Course/Data/Model/Data_CourseOutlineResponse.swift @@ -7,68 +7,89 @@ import Foundation import CoreData +import Core -struct BECourseDetailBlocks: Decodable { - let rootItem: String - typealias Blocks = [String: BECourseDetailIncoming] - var dict: Blocks - - enum CodingKeys: String, CodingKey { - case blocks - case rootItem = "root" - } - - init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) +public extension DataLayer { + struct CourseStructure: Decodable { + let rootItem: String + typealias Blocks = [String: CourseBlock] + var dict: Blocks + let id: String + let media: DataLayer.CourseMedia + let certificate: Certificate? + + enum CodingKeys: String, CodingKey { + case blocks + case rootItem = "root" + case id + case media + case certificate + } + + init(rootItem: String, dict: Blocks, id: String, media: DataLayer.CourseMedia, certificate: Certificate?) { + self.rootItem = rootItem + self.dict = dict + self.id = id + self.media = media + self.certificate = certificate + } - dict = try values.decode(Blocks.self, forKey: .blocks) - rootItem = try values.decode(String.self, forKey: .rootItem) + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + + dict = try values.decode(Blocks.self, forKey: .blocks) + rootItem = try values.decode(String.self, forKey: .rootItem) + id = try values.decode(String.self, forKey: .id) + media = try values.decode(DataLayer.CourseMedia.self, forKey: .media) + certificate = try values.decode(Certificate.self, forKey: .certificate) + } } } - -public struct BECourseDetailIncoming: Decodable { - let blockId: String - let id: String - let graded: Bool - let completion: Double? - let studentUrl: String - let type: String - let displayName: String - let descendants: [String]? - let allSources: [String]? - let userViewData: BECourseDetailUserViewData? - - public enum CodingKeys: String, CodingKey { - case id, type, descendants, graded, completion - case blockId = "block_id" - case studentUrl = "student_view_url" - case displayName = "display_name" - case userViewData = "student_view_data" - case allSources = "all_sources" +public extension DataLayer { + public struct CourseBlock: Decodable { + let blockId: String + let id: String + let graded: Bool + let completion: Double? + let studentUrl: String + let type: String + let displayName: String + let descendants: [String]? + let allSources: [String]? + let userViewData: CourseDetailUserViewData? + + public enum CodingKeys: String, CodingKey { + case id, type, descendants, graded, completion + case blockId = "block_id" + case studentUrl = "student_view_url" + case displayName = "display_name" + case userViewData = "student_view_data" + case allSources = "all_sources" + } } -} - -public struct BECourseDetailUserViewData: Decodable { - let encodedVideo: BECourseDetailEncodedVideoData? - let topicID: String? - public enum CodingKeys: String, CodingKey { - case encodedVideo = "encoded_videos" - case topicID = "topic_id" + public struct CourseDetailUserViewData: Decodable { + let encodedVideo: CourseDetailEncodedVideoData? + let topicID: String? + + public enum CodingKeys: String, CodingKey { + case encodedVideo = "encoded_videos" + case topicID = "topic_id" + } } -} - -struct BECourseDetailEncodedVideoData: Decodable { - let youTube: BECourseDetailYouTubeData? - let fallback: BECourseDetailYouTubeData? - enum CodingKeys: String, CodingKey { - case youTube = "youtube" - case fallback + struct CourseDetailEncodedVideoData: Decodable { + let youTube: CourseDetailYouTubeData? + let fallback: CourseDetailYouTubeData? + + enum CodingKeys: String, CodingKey { + case youTube = "youtube" + case fallback + } } -} - -struct BECourseDetailYouTubeData: Decodable { - let url: String? + struct CourseDetailYouTubeData: Decodable { + let url: String? + + } } diff --git a/Course/Course/Data/Network/CourseDetailsEndpoint.swift b/Course/Course/Data/Network/CourseDetailsEndpoint.swift index c3eb6887c..80e174e10 100644 --- a/Course/Course/Data/Network/CourseDetailsEndpoint.swift +++ b/Course/Course/Data/Network/CourseDetailsEndpoint.swift @@ -11,7 +11,6 @@ import Alamofire enum CourseDetailsEndpoint: EndPointType { case getCourseDetail(courseID: String) - case getEnrollments(username: String) case getCourseBlocks(courseID: String, userName: String) case pageHTML(pageUrlString: String) case enrollToCourse(courseID: String) @@ -22,11 +21,9 @@ enum CourseDetailsEndpoint: EndPointType { var path: String { switch self { case .getCourseDetail(let courseID): - return "/api/courses/v1/courses/\(courseID)" - case .getEnrollments(username: let username): - return "/api/mobile/v1/users/\(username)/course_enrollments/" + return "/mobile_api_extensions/v1/courses/\(courseID)" case .getCourseBlocks: - return "/api/courses/v1/blocks/" + return "/mobile_api_extensions/v1/blocks/" case .pageHTML(pageUrlString: let url): return "/xblock/\(url)" case .enrollToCourse: @@ -45,8 +42,6 @@ enum CourseDetailsEndpoint: EndPointType { switch self { case .getCourseDetail: return .get - case .getEnrollments: - return .get case .getCourseBlocks: return .get case .pageHTML: @@ -84,8 +79,6 @@ enum CourseDetailsEndpoint: EndPointType { "block_counts": "video" ] return .requestParameters(parameters: params, encoding: URLEncoding.queryString) - case .getEnrollments: - return .request case .pageHTML: return .request case .enrollToCourse(courseID: let courseID): diff --git a/Course/Course/Data/Persistence/CourseCoreModel.xcdatamodeld/CourseCoreModel.xcdatamodel/contents b/Course/Course/Data/Persistence/CourseCoreModel.xcdatamodeld/CourseCoreModel.xcdatamodel/contents index 02b464b9d..0395c67a9 100644 --- a/Course/Course/Data/Persistence/CourseCoreModel.xcdatamodeld/CourseCoreModel.xcdatamodel/contents +++ b/Course/Course/Data/Persistence/CourseCoreModel.xcdatamodeld/CourseCoreModel.xcdatamodel/contents @@ -1,10 +1,11 @@ - - + + - + + @@ -14,7 +15,7 @@ - + @@ -27,6 +28,7 @@ + @@ -55,4 +57,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Course/Course/Data/Persistence/CoursePersistence.swift b/Course/Course/Data/Persistence/CoursePersistence.swift index 9b591ddac..0d961731a 100644 --- a/Course/Course/Data/Persistence/CoursePersistence.swift +++ b/Course/Course/Data/Persistence/CoursePersistence.swift @@ -13,8 +13,8 @@ public protocol CoursePersistenceProtocol { func saveCourseDetails(course: CourseDetails) func loadEnrollments() throws -> [Core.CourseItem] func saveEnrollments(items: [Core.CourseItem]) - func loadCourseStructure() throws -> [BECourseDetailIncoming] - func saveCourseStructure(structure: [BECourseDetailIncoming]) + func loadCourseStructure(courseID: String) throws -> DataLayer.CourseStructure + func saveCourseStructure(structure: DataLayer.CourseStructure) func clear() } @@ -33,8 +33,8 @@ public class CoursePersistence: CoursePersistenceProtocol { }() public func loadCourseDetails(courseID: String) throws -> CourseDetails { - let request = CDCourseDetails.fetchRequest() - request.predicate = NSPredicate(format: "courseID = %@", courseID) + let request = CDCourseDetails.fetchRequest() + request.predicate = NSPredicate(format: "courseID = %@", courseID) guard let courseDetails = try? context.fetch(request).first else { throw NoCachedDataError() } return CourseDetails(courseID: courseDetails.courseID ?? "", org: courseDetails.org ?? "", @@ -44,6 +44,7 @@ public class CoursePersistence: CoursePersistenceProtocol { courseEnd: courseDetails.courseEnd, enrollmentStart: courseDetails.enrollmentStart, enrollmentEnd: courseDetails.enrollmentEnd, + isEnrolled: courseDetails.isEnrolled, overviewHTML: courseDetails.overviewHTML ?? "", courseBannerURL: courseDetails.courseBannerURL ?? "") } @@ -51,7 +52,6 @@ public class CoursePersistence: CoursePersistenceProtocol { public func saveCourseDetails(course: CourseDetails) { context.performAndWait { let newCourseDetails = CDCourseDetails(context: context) - context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump newCourseDetails.courseID = course.courseID newCourseDetails.org = course.org newCourseDetails.courseTitle = course.courseTitle @@ -60,6 +60,7 @@ public class CoursePersistence: CoursePersistenceProtocol { newCourseDetails.courseEnd = course.courseEnd newCourseDetails.enrollmentStart = course.enrollmentStart newCourseDetails.enrollmentEnd = course.enrollmentEnd + newCourseDetails.isEnrolled = course.isEnrolled newCourseDetails.overviewHTML = course.overviewHTML newCourseDetails.courseBannerURL = course.courseBannerURL @@ -84,7 +85,6 @@ public class CoursePersistence: CoursePersistenceProtocol { enrollmentStart: $0.enrollmentStart, enrollmentEnd: $0.enrollmentEnd, courseID: $0.courseID ?? "", - certificate: Certificate(url: $0.certificate ?? ""), numPages: Int($0.numPages), coursesCount: Int($0.courseCount))} if let result, !result.isEmpty { @@ -98,7 +98,6 @@ public class CoursePersistence: CoursePersistenceProtocol { context.performAndWait { for item in items { let newItem = CDCourseItem(context: context) - context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump newItem.name = item.name newItem.org = item.org newItem.desc = item.shortDescription @@ -110,7 +109,6 @@ public class CoursePersistence: CoursePersistenceProtocol { newItem.courseEnd = item.courseEnd newItem.enrollmentStart = item.enrollmentStart newItem.enrollmentEnd = item.enrollmentEnd - newItem.certificate = item.certificate?.url newItem.numPages = Int32(item.numPages) newItem.courseID = item.courseID newItem.courseCount = Int32(item.coursesCount) @@ -124,41 +122,63 @@ public class CoursePersistence: CoursePersistenceProtocol { } } - public func loadCourseStructure() throws -> [BECourseDetailIncoming] { - let result = try? context.fetch(CDCourseDetailIncoming.fetchRequest()) - .map({ - let userViewData = BECourseDetailUserViewData(encodedVideo: BECourseDetailEncodedVideoData( - youTube: BECourseDetailYouTubeData(url: $0.youTubeUrl), - fallback: BECourseDetailYouTubeData(url: $0.fallbackUrl) - ), topicID: "") - return BECourseDetailIncoming(blockId: $0.blockId ?? "", - id: $0.id ?? "", - graded: $0.graded, - completion: $0.completion, - studentUrl: $0.studentUrl ?? "", - type: $0.type ?? "", - displayName: $0.displayName ?? "", - descendants: $0.descendants, - allSources: $0.allSources, - userViewData: userViewData) - }) - if let result, !result.isEmpty { - return result - } else { - throw NoCachedDataError() + public func loadCourseStructure(courseID: String) throws -> DataLayer.CourseStructure { + let request = CDCourseStructure.fetchRequest() + request.predicate = NSPredicate(format: "id = %@", courseID) + guard let structure = try? context.fetch(request).first else { throw NoCachedDataError() } + + let requestBlocks = CDCourseBlock.fetchRequest() + requestBlocks.predicate = NSPredicate(format: "courseID = %@", courseID) + + let blocks = try? context.fetch(requestBlocks).map { + let userViewData = DataLayer.CourseDetailUserViewData( + encodedVideo: DataLayer.CourseDetailEncodedVideoData( + youTube: DataLayer.CourseDetailYouTubeData(url: $0.youTubeUrl), + fallback: DataLayer.CourseDetailYouTubeData(url: $0.fallbackUrl) + ), topicID: "") + return DataLayer.CourseBlock(blockId: $0.blockId ?? "", + id: $0.id ?? "", + graded: $0.graded, + completion: $0.completion, + studentUrl: $0.studentUrl ?? "", + type: $0.type ?? "", + displayName: $0.displayName ?? "", + descendants: $0.descendants, + allSources: $0.allSources, + userViewData: userViewData) } + + let dictionary = blocks?.reduce(into: [:]) { result, block in + result[block.id] = block + } ?? [:] + + return DataLayer.CourseStructure(rootItem: structure.rootItem ?? "", + dict: dictionary, + id: structure.id ?? "", + media: DataLayer.CourseMedia(image: + DataLayer.Image(raw: structure.mediaRaw ?? "", + small: structure.mediaSmall ?? "", + large: structure.mediaLarge ?? "")), + certificate: DataLayer.Certificate(url: structure.certificate)) } - public func saveCourseStructure(structure: [BECourseDetailIncoming]) { + public func saveCourseStructure(structure: DataLayer.CourseStructure) { context.performAndWait { - for block in structure { - let courseDetail = CDCourseDetailIncoming(context: context) - context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump - + let newStructure = CDCourseStructure(context: context) + newStructure.certificate = structure.certificate?.url + newStructure.mediaSmall = structure.media.image.small + newStructure.mediaLarge = structure.media.image.large + newStructure.mediaRaw = structure.media.image.raw + newStructure.id = structure.id + newStructure.rootItem = structure.rootItem + + for block in Array(structure.dict.values) { + let courseDetail = CDCourseBlock(context: context) courseDetail.allSources = block.allSources courseDetail.descendants = block.descendants courseDetail.graded = block.graded courseDetail.blockId = block.blockId + courseDetail.courseID = structure.id courseDetail.displayName = block.displayName courseDetail.id = block.id courseDetail.studentUrl = block.studentUrl @@ -215,6 +235,7 @@ public class CoursePersistence: CoursePersistenceProtocol { private func createContext() -> NSManagedObjectContext { let context = persistentContainer.newBackgroundContext() + context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump context.automaticallyMergesChangesFromParent = true return context } diff --git a/Course/Course/Domain/CourseInteractor.swift b/Course/Course/Domain/CourseInteractor.swift index 947cad4fd..80c29c6ca 100644 --- a/Course/Course/Domain/CourseInteractor.swift +++ b/Course/Course/Domain/CourseInteractor.swift @@ -11,12 +11,10 @@ import Core //sourcery: AutoMockable public protocol CourseInteractorProtocol { func getCourseDetails(courseID: String) async throws -> CourseDetails - func getEnrollments() async throws -> [CourseItem] func getCourseBlocks(courseID: String) async throws -> CourseStructure func getCourseVideoBlocks(fullStructure: CourseStructure) -> CourseStructure func getCourseDetailsOffline(courseID: String) async throws -> CourseDetails - func getEnrollmentsOffline() async throws -> [CourseItem] - func getCourseBlocksOffline() async throws -> CourseStructure + func getCourseBlocksOffline(courseID: String) async throws -> CourseStructure func enrollToCourse(courseID: String) async throws -> Bool func blockCompletionRequest(courseID: String, blockID: String) async throws func getHandouts(courseID: String) async throws -> String? @@ -36,10 +34,6 @@ public class CourseInteractor: CourseInteractorProtocol { return try await repository.getCourseDetails(courseID: courseID) } - public func getEnrollments() async throws -> [CourseItem] { - return try await repository.getEnrollments() - } - public func getCourseBlocks(courseID: String) async throws -> CourseStructure { return try await repository.getCourseBlocks(courseID: courseID) } @@ -60,7 +54,9 @@ public class CourseInteractor: CourseInteractorProtocol { encodedVideo: course.encodedVideo, displayName: course.displayName, topicID: course.topicID, - childs: newChilds + childs: newChilds, + media: course.media, + certificate: course.certificate ) } @@ -68,12 +64,8 @@ public class CourseInteractor: CourseInteractorProtocol { return try await repository.getCourseDetailsOffline(courseID: courseID) } - public func getEnrollmentsOffline() async throws -> [CourseItem] { - return try await repository.getEnrollmentsOffline() - } - - public func getCourseBlocksOffline() async throws -> CourseStructure { - return try repository.getCourseBlocksOffline() + public func getCourseBlocksOffline(courseID: String) async throws -> CourseStructure { + return try repository.getCourseBlocksOffline(courseID: courseID) } public func enrollToCourse(courseID: String) async throws -> Bool { diff --git a/Course/Course/Domain/Model/CourseDetails.swift b/Course/Course/Domain/Model/CourseDetails.swift index f0917d30d..d14e2b4b3 100644 --- a/Course/Course/Domain/Model/CourseDetails.swift +++ b/Course/Course/Domain/Model/CourseDetails.swift @@ -16,6 +16,7 @@ public struct CourseDetails { let courseEnd: Date? let enrollmentStart: Date? let enrollmentEnd: Date? + var isEnrolled: Bool var overviewHTML: String let courseBannerURL: String @@ -27,6 +28,7 @@ public struct CourseDetails { courseEnd: Date?, enrollmentStart: Date?, enrollmentEnd: Date?, + isEnrolled: Bool, overviewHTML: String, courseBannerURL: String) { self.courseID = courseID @@ -37,6 +39,7 @@ public struct CourseDetails { self.courseEnd = courseEnd self.enrollmentStart = enrollmentStart self.enrollmentEnd = enrollmentEnd + self.isEnrolled = isEnrolled self.overviewHTML = overviewHTML self.courseBannerURL = courseBannerURL } diff --git a/Course/Course/Presentation/Container/CourseContainerView.swift b/Course/Course/Presentation/Container/CourseContainerView.swift index de071b31b..78dd8ca7f 100644 --- a/Course/Course/Presentation/Container/CourseContainerView.swift +++ b/Course/Course/Presentation/Container/CourseContainerView.swift @@ -17,8 +17,6 @@ public struct CourseContainerView: View { @State private var selection: CourseTab = .course private var courseID: String private var title: String - private var courseBanner: String - private var certificate: Certificate? public enum CourseTab { case course @@ -30,9 +28,7 @@ public struct CourseContainerView: View { public init( viewModel: CourseContainerViewModel, courseID: String, - title: String, - courseBanner: String, - certificate: Certificate? + title: String ) { self.viewModel = viewModel Task { @@ -40,8 +36,6 @@ public struct CourseContainerView: View { } self.courseID = courseID self.title = title - self.courseBanner = courseBanner - self.certificate = certificate } public var body: some View { @@ -50,8 +44,6 @@ public struct CourseContainerView: View { CourseOutlineView( viewModel: viewModel, title: title, - courseBanner: courseBanner, - certificate: certificate, courseID: courseID, isVideo: false ) @@ -60,8 +52,6 @@ public struct CourseContainerView: View { CourseOutlineView( viewModel: self.viewModel, title: title, - courseBanner: courseBanner, - certificate: certificate, courseID: courseID, isVideo: false ) @@ -75,8 +65,6 @@ public struct CourseContainerView: View { CourseOutlineView( viewModel: self.viewModel, title: title, - courseBanner: courseBanner, - certificate: certificate, courseID: courseID, isVideo: true ) @@ -131,7 +119,7 @@ struct CourseScreensView_Previews: PreviewProvider { enrollmentStart: nil, enrollmentEnd: nil ), - courseID: "", title: "Title of Course", courseBanner: "", certificate: nil) + courseID: "", title: "Title of Course") } } #endif diff --git a/Course/Course/Presentation/Container/CourseContainerViewModel.swift b/Course/Course/Presentation/Container/CourseContainerViewModel.swift index dd1d1210d..e72704447 100644 --- a/Course/Course/Presentation/Container/CourseContainerViewModel.swift +++ b/Course/Course/Presentation/Container/CourseContainerViewModel.swift @@ -79,7 +79,7 @@ public class CourseContainerViewModel: BaseCourseViewModel { if connectivity.isInternetAvaliable { courseStructure = try await interactor.getCourseBlocks(courseID: courseID) } else { - courseStructure = try await interactor.getCourseBlocksOffline() + courseStructure = try await interactor.getCourseBlocksOffline(courseID: courseID) } courseVideosStructure = interactor.getCourseVideoBlocks(fullStructure: courseStructure!) setDownloadsStates() diff --git a/Course/Course/Presentation/CourseRouter.swift b/Course/Course/Presentation/CourseRouter.swift index 2e9a22aaf..1ec3e4f01 100644 --- a/Course/Course/Presentation/CourseRouter.swift +++ b/Course/Course/Presentation/CourseRouter.swift @@ -16,9 +16,7 @@ public protocol CourseRouter: BaseRouter { courseEnd: Date?, enrollmentStart: Date?, enrollmentEnd: Date?, - title: String, - courseBanner: String, - certificate: Certificate?) + title: String) func showCourseUnit(blockId: String, courseID: String, @@ -49,9 +47,7 @@ public class CourseRouterMock: BaseRouterMock, CourseRouter { courseEnd: Date?, enrollmentStart: Date?, enrollmentEnd: Date?, - title: String, - courseBanner: String, - certificate: Certificate?) {} + title: String) {} public func showCourseUnit(blockId: String, courseID: String, diff --git a/Course/Course/Presentation/Details/CourseDetailsView.swift b/Course/Course/Presentation/Details/CourseDetailsView.swift index 90920d525..5ef8429a9 100644 --- a/Course/Course/Presentation/Details/CourseDetailsView.swift +++ b/Course/Course/Presentation/Details/CourseDetailsView.swift @@ -106,9 +106,7 @@ public struct CourseDetailsView: View { courseEnd: courseDetails.courseEnd, enrollmentStart: courseDetails.enrollmentStart, enrollmentEnd: courseDetails.enrollmentEnd, - title: title, - courseBanner: courseDetails.courseBannerURL, - certificate: viewModel.certificate + title: title ) }) @@ -181,9 +179,7 @@ public struct CourseDetailsView: View { courseEnd: courseDetails.courseEnd, enrollmentStart: courseDetails.enrollmentStart, enrollmentEnd: courseDetails.enrollmentEnd, - title: title, - courseBanner: courseDetails.courseBannerURL, - certificate: viewModel.certificate + title: title ) }) diff --git a/Course/Course/Presentation/Details/CourseDetailsViewModel.swift b/Course/Course/Presentation/Details/CourseDetailsViewModel.swift index 6022b70c2..bc5df53cf 100644 --- a/Course/Course/Presentation/Details/CourseDetailsViewModel.swift +++ b/Course/Course/Presentation/Details/CourseDetailsViewModel.swift @@ -19,9 +19,7 @@ public class CourseDetailsViewModel: ObservableObject { @Published var courseDetails: CourseDetails? @Published private(set) var isShowProgress = false - @Published var isEnrolled: Bool = false @Published var showError: Bool = false - @Published var certificate: Certificate? @Published var isHorisontal: Bool = false var errorMessage: String? { didSet { @@ -55,15 +53,16 @@ public class CourseDetailsViewModel: ObservableObject { do { if connectivity.isInternetAvaliable { courseDetails = try await interactor.getCourseDetails(courseID: courseID) - async let enrolled = interactor.getEnrollments() - self.isEnrolled = try await enrolled.contains(where: { $0.courseID == courseID }) - self.certificate = try await enrolled.first(where: { $0.courseID == courseID })?.certificate + if let isEnrolled = courseDetails?.isEnrolled { + self.courseDetails?.isEnrolled = isEnrolled + } + isShowProgress = false } else { courseDetails = try await interactor.getCourseDetailsOffline(courseID: courseID) - async let enrolled = interactor.getEnrollmentsOffline() - self.isEnrolled = try await enrolled.contains(where: { $0.courseID == courseID }) - self.certificate = try await enrolled.first(where: { $0.courseID == courseID })?.certificate + if let isEnrolled = courseDetails?.isEnrolled { + self.courseDetails?.isEnrolled = isEnrolled + } isShowProgress = false } } catch let error { @@ -77,7 +76,7 @@ public class CourseDetailsViewModel: ObservableObject { } func courseState() -> CourseState { - if !isEnrolled { + if courseDetails?.isEnrolled == false { if let enrollmentStart = courseDetails?.enrollmentStart, let enrollmentEnd = courseDetails?.enrollmentEnd { let enrollmentsRange = DateInterval(start: enrollmentStart, end: enrollmentEnd) if enrollmentsRange.contains(Date()) { @@ -97,7 +96,7 @@ public class CourseDetailsViewModel: ObservableObject { func enrollToCourse(id: String) async { do { _ = try await interactor.enrollToCourse(courseID: id) - isEnrolled = true + courseDetails?.isEnrolled = true NotificationCenter.default.post(name: .onCourseEnrolled, object: id) } catch let error { if error.isInternetError || error is NoCachedDataError { diff --git a/Course/Course/Presentation/Outline/CourseOutlineView.swift b/Course/Course/Presentation/Outline/CourseOutlineView.swift index 3ce1e3bb2..023b3c97f 100644 --- a/Course/Course/Presentation/Outline/CourseOutlineView.swift +++ b/Course/Course/Presentation/Outline/CourseOutlineView.swift @@ -13,8 +13,6 @@ public struct CourseOutlineView: View { @ObservedObject private var viewModel: CourseContainerViewModel private let title: String - private let courseBanner: String - private let certificate: Certificate? private let courseID: String private let isVideo: Bool @@ -23,15 +21,11 @@ public struct CourseOutlineView: View { public init( viewModel: CourseContainerViewModel, title: String, - courseBanner: String, - certificate: Certificate?, courseID: String, isVideo: Bool ) { self.title = title self.viewModel = viewModel - self.courseBanner = courseBanner - self.certificate = certificate self.courseID = courseID self.isVideo = isVideo } @@ -52,14 +46,17 @@ public struct CourseOutlineView: View { VStack(alignment: .leading) { ZStack { // MARK: - Course Banner - KFImage(URL(string: self.courseBanner)) - .onFailureImage(CoreAssets.noCourseImage.image) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(maxWidth: proxy.size.width - 12, maxHeight: .infinity) + if let banner = viewModel.courseStructure?.media.image.raw + .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { + KFImage(URL(string: viewModel.config.baseURL.absoluteString + banner)) + .onFailureImage(CoreAssets.noCourseImage.image) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(maxWidth: proxy.size.width - 12, maxHeight: .infinity) + } // MARK: - Course Certificate - if let certificate { + if let certificate = viewModel.courseStructure?.certificate { if let url = certificate.url, url.count > 0 { CoreAssets.certificateForeground.swiftUIColor VStack(alignment: .center, spacing: 8) { @@ -243,8 +240,6 @@ struct CourseOutlineView_Previews: PreviewProvider { CourseOutlineView( viewModel: viewModel, title: "Course title", - courseBanner: "", - certificate: Certificate(url: "cert_url"), courseID: "", isVideo: false ) @@ -254,8 +249,6 @@ struct CourseOutlineView_Previews: PreviewProvider { CourseOutlineView( viewModel: viewModel, title: "Course title", - courseBanner: "", - certificate: Certificate(url: "cert_url"), courseID: "", isVideo: false ) diff --git a/Course/CourseTests/CourseMock.generated.swift b/Course/CourseTests/CourseMock.generated.swift index 52340c313..2dd0b655d 100644 --- a/Course/CourseTests/CourseMock.generated.swift +++ b/Course/CourseTests/CourseMock.generated.swift @@ -502,6 +502,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?() } + open func showForgotPasswordScreen() { + addInvocation(.m_showForgotPasswordScreen) + let perform = methodPerformValue(.m_showForgotPasswordScreen) as? () -> Void + perform?() + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -536,6 +542,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showMainScreen case m_showLoginScreen case m_showRegisterScreen + case m_showForgotPasswordScreen case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) @@ -571,6 +578,8 @@ open class BaseRouterMock: BaseRouter, Mock { case (.m_showRegisterScreen, .m_showRegisterScreen): return .match + case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsOktapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsOktapped, let rhsType)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) @@ -616,6 +625,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 + case .m_showForgotPasswordScreen: return 0 case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_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_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue @@ -632,6 +642,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return ".showMainScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" + case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped: return ".presentAlert(alertTitle:alertMessage:action:image:onCloseTapped:okTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" @@ -662,6 +673,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} + public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(`alertTitle`, `alertMessage`, `action`, `image`, `onCloseTapped`, `okTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} @@ -696,6 +708,9 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showRegisterScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showRegisterScreen, performs: perform) } + public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showForgotPasswordScreen, performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: 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_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) } @@ -828,10 +843,10 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { } private var __p_isInternetAvaliable: (Bool)? - public var isWifi: Bool { - get { invocations.append(.p_isWifi_get); return __p_isWifi ?? givenGetterValue(.p_isWifi_get, "ConnectivityProtocolMock - stub value for isWifi was not defined") } + 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_isWifi: (Bool)? + 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") } @@ -845,12 +860,12 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate enum MethodType { case p_isInternetAvaliable_get - case p_isWifi_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_isWifi_get,.p_isWifi_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 } @@ -859,14 +874,14 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { func intValue() -> Int { switch self { case .p_isInternetAvaliable_get: return 0 - case .p_isWifi_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_isWifi_get: return "[get] .isWifi" + case .p_isMobileData_get: return "[get] .isMobileData" case .p_internetReachableSubject_get: return "[get] .internetReachableSubject" } } @@ -883,8 +898,8 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { 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 isWifi(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_isWifi_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) })) @@ -896,7 +911,7 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate var method: MethodType public static var isInternetAvaliable: Verify { return Verify(method: .p_isInternetAvaliable_get) } - public static var isWifi: Verify { return Verify(method: .p_isWifi_get) } + public static var isMobileData: Verify { return Verify(method: .p_isMobileData_get) } public static var internetReachableSubject: Verify { return Verify(method: .p_internetReachableSubject_get) } } @@ -1039,22 +1054,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { return __value } - open func getEnrollments() throws -> [CourseItem] { - addInvocation(.m_getEnrollments) - let perform = methodPerformValue(.m_getEnrollments) as? () -> Void - perform?() - var __value: [CourseItem] - do { - __value = try methodReturnValue(.m_getEnrollments).casted() - } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for getEnrollments(). Use given") - Failure("Stub return value not specified for getEnrollments(). Use given") - } catch { - throw error - } - return __value - } - 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 @@ -1101,32 +1100,16 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { return __value } - open func getEnrollmentsOffline() throws -> [CourseItem] { - addInvocation(.m_getEnrollmentsOffline) - let perform = methodPerformValue(.m_getEnrollmentsOffline) as? () -> Void - perform?() - var __value: [CourseItem] - do { - __value = try methodReturnValue(.m_getEnrollmentsOffline).casted() - } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for getEnrollmentsOffline(). Use given") - Failure("Stub return value not specified for getEnrollmentsOffline(). Use given") - } catch { - throw error - } - return __value - } - - open func getCourseBlocksOffline() throws -> CourseStructure { - addInvocation(.m_getCourseBlocksOffline) - let perform = methodPerformValue(.m_getCourseBlocksOffline) as? () -> Void - perform?() + open func getCourseBlocksOffline(courseID: String) throws -> CourseStructure { + addInvocation(.m_getCourseBlocksOffline__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_getCourseBlocksOffline__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) var __value: CourseStructure do { - __value = try methodReturnValue(.m_getCourseBlocksOffline).casted() + __value = try methodReturnValue(.m_getCourseBlocksOffline__courseID_courseID(Parameter.value(`courseID`))).casted() } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for getCourseBlocksOffline(). Use given") - Failure("Stub return value not specified for getCourseBlocksOffline(). Use given") + onFatalFailure("Stub return value not specified for getCourseBlocksOffline(courseID: String). Use given") + Failure("Stub return value not specified for getCourseBlocksOffline(courseID: String). Use given") } catch { throw error } @@ -1196,12 +1179,10 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { fileprivate enum MethodType { case m_getCourseDetails__courseID_courseID(Parameter) - case m_getEnrollments case m_getCourseBlocks__courseID_courseID(Parameter) case m_getCourseVideoBlocks__fullStructure_fullStructure(Parameter) case m_getCourseDetailsOffline__courseID_courseID(Parameter) - case m_getEnrollmentsOffline - case m_getCourseBlocksOffline + case m_getCourseBlocksOffline__courseID_courseID(Parameter) case m_enrollToCourse__courseID_courseID(Parameter) case m_blockCompletionRequest__courseID_courseIDblockID_blockID(Parameter, Parameter) case m_getHandouts__courseID_courseID(Parameter) @@ -1214,8 +1195,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) - case (.m_getEnrollments, .m_getEnrollments): return .match - 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")) @@ -1231,9 +1210,10 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) - case (.m_getEnrollmentsOffline, .m_getEnrollmentsOffline): return .match - - case (.m_getCourseBlocksOffline, .m_getCourseBlocksOffline): return .match + case (.m_getCourseBlocksOffline__courseID_courseID(let lhsCourseid), .m_getCourseBlocksOffline__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_enrollToCourse__courseID_courseID(let lhsCourseid), .m_enrollToCourse__courseID_courseID(let rhsCourseid)): var results: [Matcher.ParameterComparisonResult] = [] @@ -1262,12 +1242,10 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_getCourseDetails__courseID_courseID(p0): return p0.intValue - case .m_getEnrollments: return 0 case let .m_getCourseBlocks__courseID_courseID(p0): return p0.intValue case let .m_getCourseVideoBlocks__fullStructure_fullStructure(p0): return p0.intValue case let .m_getCourseDetailsOffline__courseID_courseID(p0): return p0.intValue - case .m_getEnrollmentsOffline: return 0 - case .m_getCourseBlocksOffline: return 0 + case let .m_getCourseBlocksOffline__courseID_courseID(p0): return p0.intValue case let .m_enrollToCourse__courseID_courseID(p0): return p0.intValue case let .m_blockCompletionRequest__courseID_courseIDblockID_blockID(p0, p1): return p0.intValue + p1.intValue case let .m_getHandouts__courseID_courseID(p0): return p0.intValue @@ -1277,12 +1255,10 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { func assertionName() -> String { switch self { case .m_getCourseDetails__courseID_courseID: return ".getCourseDetails(courseID:)" - case .m_getEnrollments: return ".getEnrollments()" case .m_getCourseBlocks__courseID_courseID: return ".getCourseBlocks(courseID:)" case .m_getCourseVideoBlocks__fullStructure_fullStructure: return ".getCourseVideoBlocks(fullStructure:)" case .m_getCourseDetailsOffline__courseID_courseID: return ".getCourseDetailsOffline(courseID:)" - case .m_getEnrollmentsOffline: return ".getEnrollmentsOffline()" - case .m_getCourseBlocksOffline: return ".getCourseBlocksOffline()" + case .m_getCourseBlocksOffline__courseID_courseID: return ".getCourseBlocksOffline(courseID:)" case .m_enrollToCourse__courseID_courseID: return ".enrollToCourse(courseID:)" case .m_blockCompletionRequest__courseID_courseIDblockID_blockID: return ".blockCompletionRequest(courseID:blockID:)" case .m_getHandouts__courseID_courseID: return ".getHandouts(courseID:)" @@ -1303,9 +1279,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { public static func getCourseDetails(courseID: Parameter, willReturn: CourseDetails...) -> MethodStub { return Given(method: .m_getCourseDetails__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func getEnrollments(willReturn: [CourseItem]...) -> MethodStub { - return Given(method: .m_getEnrollments, products: willReturn.map({ StubProduct.return($0 as Any) })) - } 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) })) } @@ -1315,11 +1288,8 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { public static func getCourseDetailsOffline(courseID: Parameter, willReturn: CourseDetails...) -> MethodStub { return Given(method: .m_getCourseDetailsOffline__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func getEnrollmentsOffline(willReturn: [CourseItem]...) -> MethodStub { - return Given(method: .m_getEnrollmentsOffline, products: willReturn.map({ StubProduct.return($0 as Any) })) - } - public static func getCourseBlocksOffline(willReturn: CourseStructure...) -> MethodStub { - return Given(method: .m_getCourseBlocksOffline, products: willReturn.map({ StubProduct.return($0 as Any) })) + public static func getCourseBlocksOffline(courseID: Parameter, willReturn: CourseStructure...) -> MethodStub { + return Given(method: .m_getCourseBlocksOffline__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func enrollToCourse(courseID: Parameter, willReturn: Bool...) -> MethodStub { return Given(method: .m_enrollToCourse__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) @@ -1347,16 +1317,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { willProduce(stubber) return given } - public static func getEnrollments(willThrow: Error...) -> MethodStub { - return Given(method: .m_getEnrollments, products: willThrow.map({ StubProduct.throw($0) })) - } - public static func getEnrollments(willProduce: (StubberThrows<[CourseItem]>) -> Void) -> MethodStub { - let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_getEnrollments, products: willThrow.map({ StubProduct.throw($0) })) }() - let stubber = given.stubThrows(for: ([CourseItem]).self) - willProduce(stubber) - return given - } public static func getCourseBlocks(courseID: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_getCourseBlocks__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -1377,22 +1337,12 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { willProduce(stubber) return given } - public static func getEnrollmentsOffline(willThrow: Error...) -> MethodStub { - return Given(method: .m_getEnrollmentsOffline, products: willThrow.map({ StubProduct.throw($0) })) + public static func getCourseBlocksOffline(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getCourseBlocksOffline__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) } - public static func getEnrollmentsOffline(willProduce: (StubberThrows<[CourseItem]>) -> Void) -> MethodStub { + public static func getCourseBlocksOffline(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_getEnrollmentsOffline, products: willThrow.map({ StubProduct.throw($0) })) }() - let stubber = given.stubThrows(for: ([CourseItem]).self) - willProduce(stubber) - return given - } - public static func getCourseBlocksOffline(willThrow: Error...) -> MethodStub { - return Given(method: .m_getCourseBlocksOffline, products: willThrow.map({ StubProduct.throw($0) })) - } - public static func getCourseBlocksOffline(willProduce: (StubberThrows) -> Void) -> MethodStub { - let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_getCourseBlocksOffline, products: willThrow.map({ StubProduct.throw($0) })) }() + let given: Given = { return Given(method: .m_getCourseBlocksOffline__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() let stubber = given.stubThrows(for: (CourseStructure).self) willProduce(stubber) return given @@ -1443,12 +1393,10 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { fileprivate var method: MethodType public static func getCourseDetails(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseDetails__courseID_courseID(`courseID`))} - public static func getEnrollments() -> Verify { return Verify(method: .m_getEnrollments)} public static func getCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocks__courseID_courseID(`courseID`))} public static func getCourseVideoBlocks(fullStructure: Parameter) -> Verify { return Verify(method: .m_getCourseVideoBlocks__fullStructure_fullStructure(`fullStructure`))} public static func getCourseDetailsOffline(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseDetailsOffline__courseID_courseID(`courseID`))} - public static func getEnrollmentsOffline() -> Verify { return Verify(method: .m_getEnrollmentsOffline)} - public static func getCourseBlocksOffline() -> Verify { return Verify(method: .m_getCourseBlocksOffline)} + public static func getCourseBlocksOffline(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocksOffline__courseID_courseID(`courseID`))} public static func enrollToCourse(courseID: Parameter) -> Verify { return Verify(method: .m_enrollToCourse__courseID_courseID(`courseID`))} public static func blockCompletionRequest(courseID: Parameter, blockID: Parameter) -> Verify { return Verify(method: .m_blockCompletionRequest__courseID_courseIDblockID_blockID(`courseID`, `blockID`))} public static func getHandouts(courseID: Parameter) -> Verify { return Verify(method: .m_getHandouts__courseID_courseID(`courseID`))} @@ -1462,9 +1410,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { public static func getCourseDetails(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getCourseDetails__courseID_courseID(`courseID`), performs: perform) } - public static func getEnrollments(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_getEnrollments, performs: perform) - } public static func getCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getCourseBlocks__courseID_courseID(`courseID`), performs: perform) } @@ -1474,11 +1419,8 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { public static func getCourseDetailsOffline(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getCourseDetailsOffline__courseID_courseID(`courseID`), performs: perform) } - public static func getEnrollmentsOffline(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_getEnrollmentsOffline, performs: perform) - } - public static func getCourseBlocksOffline(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_getCourseBlocksOffline, performs: perform) + public static func getCourseBlocksOffline(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_getCourseBlocksOffline__courseID_courseID(`courseID`), performs: perform) } public static func enrollToCourse(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_enrollToCourse__courseID_courseID(`courseID`), performs: perform) diff --git a/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift b/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift index 7af636196..85255717f 100644 --- a/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift +++ b/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift @@ -81,7 +81,11 @@ final class CourseContainerViewModelTests: XCTestCase { encodedVideo: "", displayName: "", topicID: nil, - childs: childs + childs: childs, + media: DataLayer.CourseMedia(image: DataLayer.Image(raw: "", + small: "", + large: "")), + certificate: nil ) Given(interactor, .getCourseBlocks(courseID: "123", @@ -127,15 +131,19 @@ final class CourseContainerViewModelTests: XCTestCase { encodedVideo: "", displayName: "", topicID: nil, - childs: []) + childs: [], + media: DataLayer.CourseMedia(image: DataLayer.Image(raw: "", + small: "", + large: "")), + certificate: nil) - Given(interactor, .getCourseBlocksOffline(willReturn: courseStructure)) + Given(interactor, .getCourseBlocksOffline(courseID: .any, willReturn: courseStructure)) Given(interactor, .getCourseVideoBlocks(fullStructure: .any, willReturn: courseStructure)) await viewModel.getCourseBlocks(courseID: "123") - Verify(interactor, .getCourseBlocksOffline()) + Verify(interactor, .getCourseBlocksOffline(courseID: .any)) Verify(interactor, .getCourseVideoBlocks(fullStructure: .any)) XCTAssertFalse(viewModel.isShowProgress) XCTAssertFalse(viewModel.showError) diff --git a/Course/CourseTests/Presentation/Details/CourseDetailsViewModelTests.swift b/Course/CourseTests/Presentation/Details/CourseDetailsViewModelTests.swift index e8756c7b1..d2cf1835e 100644 --- a/Course/CourseTests/Presentation/Details/CourseDetailsViewModelTests.swift +++ b/Course/CourseTests/Presentation/Details/CourseDetailsViewModelTests.swift @@ -40,7 +40,6 @@ final class CourseDetailsViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "123", - certificate: nil, numPages: 2, coursesCount: 2), CourseItem(name: "Test2", @@ -53,7 +52,6 @@ final class CourseDetailsViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "1243", - certificate: nil, numPages: 1, coursesCount: 2) ] @@ -67,18 +65,17 @@ final class CourseDetailsViewModelTests: XCTestCase { courseEnd: nil, enrollmentStart: nil, enrollmentEnd: nil, + isEnrolled: true, overviewHTML: "", courseBannerURL: "" ) - Given(interactor, .getEnrollments(willReturn: items)) Given(interactor, .getCourseDetails(courseID: "123", willReturn: courseDetails)) await viewModel.getCourseDetail(courseID: "123") - Verify(interactor, 1, .getEnrollments()) Verify(interactor, 1, .getCourseDetails(courseID: .any)) XCTAssertFalse(viewModel.isShowProgress) @@ -112,7 +109,6 @@ final class CourseDetailsViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "123", - certificate: nil, numPages: 2, coursesCount: 2), CourseItem(name: "Test2", @@ -125,7 +121,6 @@ final class CourseDetailsViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "1243", - certificate: nil, numPages: 1, coursesCount: 2) ] @@ -139,17 +134,16 @@ final class CourseDetailsViewModelTests: XCTestCase { courseEnd: nil, enrollmentStart: nil, enrollmentEnd: nil, + isEnrolled: true, overviewHTML: "", courseBannerURL: "" ) - Given(interactor, .getEnrollmentsOffline(willReturn: items)) Given(interactor, .getCourseDetailsOffline(courseID: "123", willReturn: courseDetails)) await viewModel.getCourseDetail(courseID: "123") - Verify(interactor, 1, .getEnrollmentsOffline()) Verify(interactor, 1, .getCourseDetailsOffline(courseID: .any)) XCTAssertFalse(viewModel.isShowProgress) diff --git a/Dashboard/Dashboard/Data/DashboardRepository.swift b/Dashboard/Dashboard/Data/DashboardRepository.swift index 99596d5e8..0537afea9 100644 --- a/Dashboard/Dashboard/Data/DashboardRepository.swift +++ b/Dashboard/Dashboard/Data/DashboardRepository.swift @@ -9,7 +9,7 @@ import Foundation import Core public protocol DashboardRepositoryProtocol { - func getMyCourses() async throws -> [CourseItem] + func getMyCourses(page: Int) async throws -> [CourseItem] func getMyCoursesOffline() throws -> [CourseItem] } @@ -27,16 +27,13 @@ public class DashboardRepository: DashboardRepositoryProtocol { self.persistence = persistence } - public func getMyCourses() async throws -> [CourseItem] { + public func getMyCourses(page: Int) async throws -> [CourseItem] { let result = try await api.requestData( - DashboardEndpoint.getMyCourses(username: appStorage.user?.username ?? "") + DashboardEndpoint.getMyCourses(username: appStorage.user?.username ?? "", page: page) ) - .mapResponse([DataLayer.MyCourse].self) - .map({ course in - course.domain(baseURL: config.baseURL.absoluteString) - }) + .mapResponse(DataLayer.CourseEnrollments.self) + .domain(baseURL: config.baseURL.absoluteString) persistence.saveMyCourses(items: result) - return result } @@ -50,7 +47,7 @@ public class DashboardRepository: DashboardRepositoryProtocol { // Mark - For testing and SwiftUI preview #if DEBUG class DashboardRepositoryMock: DashboardRepositoryProtocol { - func getMyCourses() async throws -> [CourseItem] { + func getMyCourses(page: Int) async throws -> [CourseItem] { var models: [CourseItem] = [] for i in 0...10 { models.append( @@ -65,7 +62,6 @@ class DashboardRepositoryMock: DashboardRepositoryProtocol { enrollmentStart: nil, enrollmentEnd: nil, courseID: "course_id_\(i)", - certificate: nil, numPages: 1, coursesCount: 0 ) diff --git a/Dashboard/Dashboard/Data/Network/DashboardEndpoint.swift b/Dashboard/Dashboard/Data/Network/DashboardEndpoint.swift index 50a5de494..02903fab6 100644 --- a/Dashboard/Dashboard/Data/Network/DashboardEndpoint.swift +++ b/Dashboard/Dashboard/Data/Network/DashboardEndpoint.swift @@ -10,12 +10,12 @@ import Core import Alamofire enum DashboardEndpoint: EndPointType { - case getMyCourses(username: String) + case getMyCourses(username: String, page: Int) var path: String { switch self { - case .getMyCourses(let username): - return "/api/mobile/v1/users/\(username)/course_enrollments/" + case let .getMyCourses(username, _): + return "/mobile_api_extensions/v1/users/\(username)/course_enrollments" } } @@ -32,8 +32,11 @@ enum DashboardEndpoint: EndPointType { var task: HTTPTask { switch self { - case .getMyCourses: - return .request + case let .getMyCourses(_, page): + let params: Parameters = [ + "page": page + ] + return .requestParameters(parameters: params, encoding: URLEncoding.queryString) } } } diff --git a/Dashboard/Dashboard/Data/Persistence/DashboardPersistence.swift b/Dashboard/Dashboard/Data/Persistence/DashboardPersistence.swift index 0f35a82ed..f2b109d76 100644 --- a/Dashboard/Dashboard/Data/Persistence/DashboardPersistence.swift +++ b/Dashboard/Dashboard/Data/Persistence/DashboardPersistence.swift @@ -40,7 +40,6 @@ public class DashboardPersistence: DashboardPersistenceProtocol { enrollmentStart: $0.enrollmentStart, enrollmentEnd: $0.enrollmentEnd, courseID: $0.courseID ?? "", - certificate: Certificate(url: $0.certificate ?? ""), numPages: Int($0.numPages), coursesCount: Int($0.courseCount))} if let result, !result.isEmpty { @@ -63,7 +62,6 @@ public class DashboardPersistence: DashboardPersistenceProtocol { newItem.courseEnd = item.courseEnd newItem.enrollmentStart = item.enrollmentStart newItem.enrollmentEnd = item.enrollmentEnd - newItem.certificate = item.certificate?.url newItem.numPages = Int32(item.numPages) newItem.courseID = item.courseID diff --git a/Dashboard/Dashboard/Domain/DashboardInteractor.swift b/Dashboard/Dashboard/Domain/DashboardInteractor.swift index 7e8e3b851..8e84d847b 100644 --- a/Dashboard/Dashboard/Domain/DashboardInteractor.swift +++ b/Dashboard/Dashboard/Domain/DashboardInteractor.swift @@ -10,7 +10,7 @@ import Core //sourcery: AutoMockable public protocol DashboardInteractorProtocol { - func getMyCourses() async throws -> [CourseItem] + func getMyCourses(page: Int) async throws -> [CourseItem] func discoveryOffline() throws -> [CourseItem] } @@ -23,8 +23,8 @@ public class DashboardInteractor: DashboardInteractorProtocol { } @discardableResult - public func getMyCourses() async throws -> [CourseItem] { - return try await repository.getMyCourses() + public func getMyCourses(page: Int) async throws -> [CourseItem] { + return try await repository.getMyCourses(page: page) } public func discoveryOffline() throws -> [CourseItem] { diff --git a/Dashboard/Dashboard/Presentation/DashboardRouter.swift b/Dashboard/Dashboard/Presentation/DashboardRouter.swift index 3dc63ac1a..40bc86c41 100644 --- a/Dashboard/Dashboard/Presentation/DashboardRouter.swift +++ b/Dashboard/Dashboard/Presentation/DashboardRouter.swift @@ -16,9 +16,7 @@ public protocol DashboardRouter: BaseRouter { courseEnd: Date?, enrollmentStart: Date?, enrollmentEnd: Date?, - title: String, - courseBanner: String, - certificate: Certificate?) + title: String) } @@ -34,9 +32,7 @@ public class DashboardRouterMock: BaseRouterMock, DashboardRouter { courseEnd: Date?, enrollmentStart: Date?, enrollmentEnd: Date?, - title: String, - courseBanner: String, - certificate: Certificate?) {} + title: String) {} } #endif diff --git a/Dashboard/Dashboard/Presentation/DashboardView.swift b/Dashboard/Dashboard/Presentation/DashboardView.swift index dbe9dfeda..cc3430c26 100644 --- a/Dashboard/Dashboard/Presentation/DashboardView.swift +++ b/Dashboard/Dashboard/Presentation/DashboardView.swift @@ -27,7 +27,7 @@ public struct DashboardView: View { self.viewModel = viewModel self.router = router Task { - await viewModel.getMyCourses() + await viewModel.getMyCourses(page: 1) } } @@ -44,7 +44,9 @@ public struct DashboardView: View { ZStack { RefreshableScrollViewCompat(action: { - await viewModel.getMyCourses(withProgress: isIOS14) + await viewModel.getMyCourses(page: 1, + withProgress: isIOS14, + refresh: true) }) { if viewModel.courses.isEmpty { EmptyPageIcon() @@ -56,17 +58,22 @@ public struct DashboardView: View { .padding(.bottom, 20) Spacer() }.padding(.leading, 10) - ForEach(viewModel.courses, id: \.courseID) { course in - let index = viewModel.courses.firstIndex(where: {$0.courseID == course.courseID}) + ForEach(Array(viewModel.courses.enumerated()), + id: \.offset) { index, course in CourseCellView( model: course, type: .dashboard, - index: index ?? 0, + index: index, cellsCount: viewModel.courses.count ) .padding(.horizontal, 20) .listRowBackground(Color.clear) + .onAppear { + Task { + await viewModel.getMyCoursesPagination(index: index) + } + } .onTapGesture { router.showCourseScreens( courseID: course.courseID, @@ -75,28 +82,31 @@ public struct DashboardView: View { courseEnd: course.courseEnd, enrollmentStart: course.enrollmentStart, enrollmentEnd: course.enrollmentEnd, - title: course.name, - courseBanner: course.imageURL, - certificate: course.certificate + title: course.name ) } } + // MARK: - ProgressBar + if viewModel.nextPage <= viewModel.totalPages { + VStack(alignment: .center) { + ProgressBar(size: 40, lineWidth: 8) + .padding(.top, 20) + }.frame(maxWidth: .infinity, + maxHeight: .infinity) + } VStack {}.frame(height: 40) } } }.frameLimit() - - // MARK: - ProgressBar - if viewModel.isShowProgress { - ProgressBar(size: 40, lineWidth: 8) - } } } // MARK: - Offline mode SnackBar OfflineSnackBarView(connectivity: viewModel.connectivity, reloadAction: { - await viewModel.getMyCourses(withProgress: isIOS14) + await viewModel.getMyCourses( page: 1, + withProgress: isIOS14, + refresh: true) }) // MARK: - Error Alert diff --git a/Dashboard/Dashboard/Presentation/DashboardViewModel.swift b/Dashboard/Dashboard/Presentation/DashboardViewModel.swift index b6ee42baa..1b5711508 100644 --- a/Dashboard/Dashboard/Presentation/DashboardViewModel.swift +++ b/Dashboard/Dashboard/Presentation/DashboardViewModel.swift @@ -12,8 +12,11 @@ import Combine public class DashboardViewModel: ObservableObject { - @Published private(set) var courses: [CourseItem] = [] - @Published private(set) var isShowProgress = false + public var nextPage = 1 + public var totalPages = 1 + public private(set) var fetchInProgress = false + + @Published var courses: [CourseItem] = [] @Published var showError: Bool = false var errorMessage: String? { didSet { @@ -37,24 +40,34 @@ public class DashboardViewModel: ObservableObject { .sink { [weak self] _ in guard let self = self else { return } Task { - await self.getMyCourses() + await self.getMyCourses(page: 1) } } } @MainActor - func getMyCourses(withProgress: Bool = true) async { - isShowProgress = withProgress + public func getMyCourses(page: Int, withProgress: Bool = true, refresh: Bool = false) async { do { if connectivity.isInternetAvaliable { - courses = try await interactor.getMyCourses() - isShowProgress = false + if refresh { + courses = try await interactor.getMyCourses(page: page) + self.totalPages = 1 + self.nextPage = 2 + } else { + courses += try await interactor.getMyCourses(page: page) + self.nextPage += 1 + } + if !courses.isEmpty { + totalPages = courses[0].numPages + } + fetchInProgress = false } else { courses = try interactor.discoveryOffline() - isShowProgress = false + self.nextPage += 1 + fetchInProgress = false } } catch let error { - isShowProgress = false + fetchInProgress = false if error is NoCachedDataError { errorMessage = CoreLocalization.Error.noCachedData } else { @@ -62,4 +75,19 @@ public class DashboardViewModel: ObservableObject { } } } + + @MainActor + public func getMyCoursesPagination(index: Int, withProgress: Bool = true) async { + if !fetchInProgress { + if totalPages > 1 { + if index == courses.count - 3 { + if totalPages != 1 { + if nextPage <= totalPages { + await getMyCourses(page: self.nextPage, withProgress: withProgress) + } + } + } + } + } + } } diff --git a/Dashboard/DashboardTests/DashboardMock.generated.swift b/Dashboard/DashboardTests/DashboardMock.generated.swift index 4440ff962..d8e9e8131 100644 --- a/Dashboard/DashboardTests/DashboardMock.generated.swift +++ b/Dashboard/DashboardTests/DashboardMock.generated.swift @@ -502,6 +502,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?() } + open func showForgotPasswordScreen() { + addInvocation(.m_showForgotPasswordScreen) + let perform = methodPerformValue(.m_showForgotPasswordScreen) as? () -> Void + perform?() + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -536,6 +542,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showMainScreen case m_showLoginScreen case m_showRegisterScreen + case m_showForgotPasswordScreen case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) @@ -571,6 +578,8 @@ open class BaseRouterMock: BaseRouter, Mock { case (.m_showRegisterScreen, .m_showRegisterScreen): return .match + case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsOktapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsOktapped, let rhsType)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) @@ -616,6 +625,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 + case .m_showForgotPasswordScreen: return 0 case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_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_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue @@ -632,6 +642,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return ".showMainScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" + case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped: return ".presentAlert(alertTitle:alertMessage:action:image:onCloseTapped:okTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" @@ -662,6 +673,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} + public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(`alertTitle`, `alertMessage`, `action`, `image`, `onCloseTapped`, `okTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} @@ -696,6 +708,9 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showRegisterScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showRegisterScreen, performs: perform) } + public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showForgotPasswordScreen, performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: 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_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) } @@ -828,10 +843,10 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { } private var __p_isInternetAvaliable: (Bool)? - public var isWifi: Bool { - get { invocations.append(.p_isWifi_get); return __p_isWifi ?? givenGetterValue(.p_isWifi_get, "ConnectivityProtocolMock - stub value for isWifi was not defined") } + 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_isWifi: (Bool)? + 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") } @@ -845,12 +860,12 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate enum MethodType { case p_isInternetAvaliable_get - case p_isWifi_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_isWifi_get,.p_isWifi_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 } @@ -859,14 +874,14 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { func intValue() -> Int { switch self { case .p_isInternetAvaliable_get: return 0 - case .p_isWifi_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_isWifi_get: return "[get] .isWifi" + case .p_isMobileData_get: return "[get] .isMobileData" case .p_internetReachableSubject_get: return "[get] .internetReachableSubject" } } @@ -883,8 +898,8 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { 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 isWifi(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_isWifi_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) })) @@ -896,7 +911,7 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate var method: MethodType public static var isInternetAvaliable: Verify { return Verify(method: .p_isInternetAvaliable_get) } - public static var isWifi: Verify { return Verify(method: .p_isWifi_get) } + public static var isMobileData: Verify { return Verify(method: .p_isMobileData_get) } public static var internetReachableSubject: Verify { return Verify(method: .p_internetReachableSubject_get) } } @@ -1023,16 +1038,16 @@ open class DashboardInteractorProtocolMock: DashboardInteractorProtocol, Mock { - open func getMyCourses() throws -> [CourseItem] { - addInvocation(.m_getMyCourses) - let perform = methodPerformValue(.m_getMyCourses) as? () -> Void - perform?() + open func getMyCourses(page: Int) throws -> [CourseItem] { + addInvocation(.m_getMyCourses__page_page(Parameter.value(`page`))) + let perform = methodPerformValue(.m_getMyCourses__page_page(Parameter.value(`page`))) as? (Int) -> Void + perform?(`page`) var __value: [CourseItem] do { - __value = try methodReturnValue(.m_getMyCourses).casted() + __value = try methodReturnValue(.m_getMyCourses__page_page(Parameter.value(`page`))).casted() } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for getMyCourses(). Use given") - Failure("Stub return value not specified for getMyCourses(). Use given") + onFatalFailure("Stub return value not specified for getMyCourses(page: Int). Use given") + Failure("Stub return value not specified for getMyCourses(page: Int). Use given") } catch { throw error } @@ -1057,12 +1072,15 @@ open class DashboardInteractorProtocolMock: DashboardInteractorProtocol, Mock { fileprivate enum MethodType { - case m_getMyCourses + case m_getMyCourses__page_page(Parameter) case m_discoveryOffline static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { - case (.m_getMyCourses, .m_getMyCourses): return .match + case (.m_getMyCourses__page_page(let lhsPage), .m_getMyCourses__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_discoveryOffline, .m_discoveryOffline): return .match default: return .none @@ -1071,13 +1089,13 @@ open class DashboardInteractorProtocolMock: DashboardInteractorProtocol, Mock { func intValue() -> Int { switch self { - case .m_getMyCourses: return 0 + case let .m_getMyCourses__page_page(p0): return p0.intValue case .m_discoveryOffline: return 0 } } func assertionName() -> String { switch self { - case .m_getMyCourses: return ".getMyCourses()" + case .m_getMyCourses__page_page: return ".getMyCourses(page:)" case .m_discoveryOffline: return ".discoveryOffline()" } } @@ -1092,18 +1110,18 @@ open class DashboardInteractorProtocolMock: DashboardInteractorProtocol, Mock { } - public static func getMyCourses(willReturn: [CourseItem]...) -> MethodStub { - return Given(method: .m_getMyCourses, products: willReturn.map({ StubProduct.return($0 as Any) })) + public static func getMyCourses(page: Parameter, willReturn: [CourseItem]...) -> MethodStub { + return Given(method: .m_getMyCourses__page_page(`page`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func discoveryOffline(willReturn: [CourseItem]...) -> MethodStub { return Given(method: .m_discoveryOffline, products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func getMyCourses(willThrow: Error...) -> MethodStub { - return Given(method: .m_getMyCourses, products: willThrow.map({ StubProduct.throw($0) })) + public static func getMyCourses(page: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getMyCourses__page_page(`page`), products: willThrow.map({ StubProduct.throw($0) })) } - public static func getMyCourses(willProduce: (StubberThrows<[CourseItem]>) -> Void) -> MethodStub { + public static func getMyCourses(page: Parameter, willProduce: (StubberThrows<[CourseItem]>) -> Void) -> MethodStub { let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_getMyCourses, products: willThrow.map({ StubProduct.throw($0) })) }() + let given: Given = { return Given(method: .m_getMyCourses__page_page(`page`), products: willThrow.map({ StubProduct.throw($0) })) }() let stubber = given.stubThrows(for: ([CourseItem]).self) willProduce(stubber) return given @@ -1123,7 +1141,7 @@ open class DashboardInteractorProtocolMock: DashboardInteractorProtocol, Mock { public struct Verify { fileprivate var method: MethodType - public static func getMyCourses() -> Verify { return Verify(method: .m_getMyCourses)} + public static func getMyCourses(page: Parameter) -> Verify { return Verify(method: .m_getMyCourses__page_page(`page`))} public static func discoveryOffline() -> Verify { return Verify(method: .m_discoveryOffline)} } @@ -1131,8 +1149,8 @@ open class DashboardInteractorProtocolMock: DashboardInteractorProtocol, Mock { fileprivate var method: MethodType var performs: Any - public static func getMyCourses(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_getMyCourses, performs: perform) + public static func getMyCourses(page: Parameter, perform: @escaping (Int) -> Void) -> Perform { + return Perform(method: .m_getMyCourses__page_page(`page`), performs: perform) } public static func discoveryOffline(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_discoveryOffline, performs: perform) diff --git a/Dashboard/DashboardTests/Presentation/DashboardViewModelTests.swift b/Dashboard/DashboardTests/Presentation/DashboardViewModelTests.swift index a4b8c82e6..8fda4d939 100644 --- a/Dashboard/DashboardTests/Presentation/DashboardViewModelTests.swift +++ b/Dashboard/DashboardTests/Presentation/DashboardViewModelTests.swift @@ -30,7 +30,6 @@ final class DashboardViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "123", - certificate: nil, numPages: 2, coursesCount: 2), CourseItem(name: "Test2", @@ -43,21 +42,19 @@ final class DashboardViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "1243", - certificate: nil, numPages: 1, coursesCount: 2) ] Given(connectivity, .isInternetAvaliable(getter: true)) - Given(interactor, .getMyCourses(willReturn: items)) + Given(interactor, .getMyCourses(page: .any, willReturn: items)) - await viewModel.getMyCourses() + await viewModel.getMyCourses(page: 1) - Verify(interactor, 1, .getMyCourses()) + Verify(interactor, 1, .getMyCourses(page: .value(1))) XCTAssertTrue(viewModel.courses == items) XCTAssertNil(viewModel.errorMessage) - XCTAssertFalse(viewModel.isShowProgress) XCTAssertFalse(viewModel.showError) } @@ -77,7 +74,6 @@ final class DashboardViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "123", - certificate: nil, numPages: 2, coursesCount: 2), CourseItem(name: "Test2", @@ -90,7 +86,6 @@ final class DashboardViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "1243", - certificate: nil, numPages: 1, coursesCount: 2) ] @@ -98,13 +93,12 @@ final class DashboardViewModelTests: XCTestCase { Given(connectivity, .isInternetAvaliable(getter: false)) Given(interactor, .discoveryOffline(willReturn: items)) - await viewModel.getMyCourses() + await viewModel.getMyCourses(page: 1) Verify(interactor, 1, .discoveryOffline()) XCTAssertTrue(viewModel.courses == items) XCTAssertNil(viewModel.errorMessage) - XCTAssertFalse(viewModel.isShowProgress) XCTAssertFalse(viewModel.showError) } @@ -114,15 +108,14 @@ final class DashboardViewModelTests: XCTestCase { let viewModel = DashboardViewModel(interactor: interactor, connectivity: connectivity) Given(connectivity, .isInternetAvaliable(getter: true)) - Given(interactor, .getMyCourses(willThrow: NoCachedDataError()) ) + Given(interactor, .getMyCourses(page: .any, willThrow: NoCachedDataError()) ) - await viewModel.getMyCourses() + await viewModel.getMyCourses(page: 1) - Verify(interactor, 1, .getMyCourses()) + Verify(interactor, 1, .getMyCourses(page: .value(1))) XCTAssertTrue(viewModel.courses.isEmpty) XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.noCachedData) - XCTAssertFalse(viewModel.isShowProgress) XCTAssertTrue(viewModel.showError) } @@ -132,15 +125,14 @@ final class DashboardViewModelTests: XCTestCase { let viewModel = DashboardViewModel(interactor: interactor, connectivity: connectivity) Given(connectivity, .isInternetAvaliable(getter: true)) - Given(interactor, .getMyCourses(willThrow: NSError()) ) + Given(interactor, .getMyCourses(page: .any, willThrow: NSError()) ) - await viewModel.getMyCourses() + await viewModel.getMyCourses(page: 1) - Verify(interactor, 1, .getMyCourses()) + Verify(interactor, 1, .getMyCourses(page: .value(1))) XCTAssertTrue(viewModel.courses.isEmpty) XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.unknownError) - XCTAssertFalse(viewModel.isShowProgress) XCTAssertTrue(viewModel.showError) } } diff --git a/Discovery/Discovery/Data/DiscoveryRepository.swift b/Discovery/Discovery/Data/DiscoveryRepository.swift index 6719f5bae..021293fbf 100644 --- a/Discovery/Discovery/Data/DiscoveryRepository.swift +++ b/Discovery/Discovery/Data/DiscoveryRepository.swift @@ -72,7 +72,7 @@ class DiscoveryRepositoryMock: DiscoveryRepositoryProtocol { enrollmentStart: nil, enrollmentEnd: nil, courseID: "course_id_\(i)", - certificate: nil, numPages: 1, coursesCount: 10 + numPages: 1, coursesCount: 10 ) ) } @@ -94,7 +94,7 @@ class DiscoveryRepositoryMock: DiscoveryRepositoryProtocol { enrollmentStart: nil, enrollmentEnd: nil, courseID: "course_id_\(i)", - certificate: nil, numPages: 1, coursesCount: 10 + numPages: 1, coursesCount: 10 ) ) } @@ -116,7 +116,6 @@ class DiscoveryRepositoryMock: DiscoveryRepositoryProtocol { enrollmentStart: nil, enrollmentEnd: nil, courseID: "course_id_\(i)", - certificate: nil, numPages: 1, coursesCount: 10 ) diff --git a/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift b/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift index 66e8e0652..7ce334202 100644 --- a/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift +++ b/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift @@ -15,8 +15,10 @@ enum DiscoveryEndpoint: EndPointType { var path: String { switch self { - case .getDiscovery, .searchCourses: + case .getDiscovery: return "/api/courses/v1/courses/" + case .searchCourses: + return "/mobile_api_extensions/courses/v1/courses/" } } diff --git a/Discovery/Discovery/Data/Persistence/DiscoveryPersistence.swift b/Discovery/Discovery/Data/Persistence/DiscoveryPersistence.swift index 82c314527..55b0346f0 100644 --- a/Discovery/Discovery/Data/Persistence/DiscoveryPersistence.swift +++ b/Discovery/Discovery/Data/Persistence/DiscoveryPersistence.swift @@ -40,7 +40,6 @@ public class DiscoveryPersistence: DiscoveryPersistenceProtocol { enrollmentStart: $0.enrollmentStart, enrollmentEnd: $0.enrollmentEnd, courseID: $0.courseID ?? "", - certificate: Certificate(url: $0.certificate ?? ""), numPages: Int($0.numPages), coursesCount: Int($0.courseCount))} if let result, !result.isEmpty { @@ -66,7 +65,6 @@ public class DiscoveryPersistence: DiscoveryPersistenceProtocol { newItem.courseEnd = item.courseEnd newItem.enrollmentStart = item.enrollmentStart newItem.enrollmentEnd = item.enrollmentEnd - newItem.certificate = item.certificate?.url newItem.numPages = Int32(item.numPages) newItem.courseID = item.courseID diff --git a/Discovery/Discovery/Presentation/DiscoveryView.swift b/Discovery/Discovery/Presentation/DiscoveryView.swift index fe389b809..492493898 100644 --- a/Discovery/Discovery/Presentation/DiscoveryView.swift +++ b/Discovery/Discovery/Presentation/DiscoveryView.swift @@ -86,7 +86,8 @@ public struct DiscoveryView: View { .padding(.bottom, 20) Spacer() }.padding(.leading, 10) - ForEach(Array(viewModel.courses.enumerated()), id: \.offset) { index, course in + ForEach(Array(viewModel.courses.enumerated()), + id: \.offset) { index, course in CourseCellView(model: course, type: .discovery, index: index, @@ -122,6 +123,9 @@ public struct DiscoveryView: View { // MARK: - Offline mode SnackBar OfflineSnackBarView(connectivity: viewModel.connectivity, reloadAction: { + viewModel.courses = [] + viewModel.totalPages = 1 + viewModel.nextPage = 1 await viewModel.discovery(page: 1, withProgress: isIOS14) }) diff --git a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift index 4f702147e..77f59e84b 100644 --- a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift +++ b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift @@ -502,6 +502,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?() } + open func showForgotPasswordScreen() { + addInvocation(.m_showForgotPasswordScreen) + let perform = methodPerformValue(.m_showForgotPasswordScreen) as? () -> Void + perform?() + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -536,6 +542,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showMainScreen case m_showLoginScreen case m_showRegisterScreen + case m_showForgotPasswordScreen case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) @@ -571,6 +578,8 @@ open class BaseRouterMock: BaseRouter, Mock { case (.m_showRegisterScreen, .m_showRegisterScreen): return .match + case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsOktapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsOktapped, let rhsType)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) @@ -616,6 +625,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 + case .m_showForgotPasswordScreen: return 0 case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_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_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue @@ -632,6 +642,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return ".showMainScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" + case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped: return ".presentAlert(alertTitle:alertMessage:action:image:onCloseTapped:okTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" @@ -662,6 +673,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} + public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(`alertTitle`, `alertMessage`, `action`, `image`, `onCloseTapped`, `okTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} @@ -696,6 +708,9 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showRegisterScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showRegisterScreen, performs: perform) } + public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showForgotPasswordScreen, performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: 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_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) } @@ -828,10 +843,10 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { } private var __p_isInternetAvaliable: (Bool)? - public var isWifi: Bool { - get { invocations.append(.p_isWifi_get); return __p_isWifi ?? givenGetterValue(.p_isWifi_get, "ConnectivityProtocolMock - stub value for isWifi was not defined") } + 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_isWifi: (Bool)? + 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") } @@ -845,12 +860,12 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate enum MethodType { case p_isInternetAvaliable_get - case p_isWifi_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_isWifi_get,.p_isWifi_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 } @@ -859,14 +874,14 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { func intValue() -> Int { switch self { case .p_isInternetAvaliable_get: return 0 - case .p_isWifi_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_isWifi_get: return "[get] .isWifi" + case .p_isMobileData_get: return "[get] .isMobileData" case .p_internetReachableSubject_get: return "[get] .internetReachableSubject" } } @@ -883,8 +898,8 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { 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 isWifi(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_isWifi_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) })) @@ -896,7 +911,7 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate var method: MethodType public static var isInternetAvaliable: Verify { return Verify(method: .p_isInternetAvaliable_get) } - public static var isWifi: Verify { return Verify(method: .p_isWifi_get) } + public static var isMobileData: Verify { return Verify(method: .p_isMobileData_get) } public static var internetReachableSubject: Verify { return Verify(method: .p_internetReachableSubject_get) } } diff --git a/Discovery/DiscoveryTests/Presentation/DiscoveryViewModelTests.swift b/Discovery/DiscoveryTests/Presentation/DiscoveryViewModelTests.swift index f51845557..6a5a41992 100644 --- a/Discovery/DiscoveryTests/Presentation/DiscoveryViewModelTests.swift +++ b/Discovery/DiscoveryTests/Presentation/DiscoveryViewModelTests.swift @@ -38,7 +38,6 @@ final class DiscoveryViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "123", - certificate: nil, numPages: 2, coursesCount: 2), CourseItem(name: "Test2", @@ -51,7 +50,6 @@ final class DiscoveryViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "1243", - certificate: nil, numPages: 1, coursesCount: 2) ] @@ -84,7 +82,6 @@ final class DiscoveryViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "123", - certificate: nil, numPages: 2, coursesCount: 0), CourseItem(name: "Test2", @@ -97,7 +94,6 @@ final class DiscoveryViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "1243", - certificate: nil, numPages: 1, coursesCount: 0) ] @@ -129,7 +125,6 @@ final class DiscoveryViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "123", - certificate: nil, numPages: 2, coursesCount: 2), CourseItem(name: "Test2", @@ -142,7 +137,6 @@ final class DiscoveryViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "1243", - certificate: nil, numPages: 1, coursesCount: 2) ] diff --git a/Discovery/DiscoveryTests/Presentation/SearchViewModelTests.swift b/Discovery/DiscoveryTests/Presentation/SearchViewModelTests.swift index e5b3293e1..86ddc8ff4 100644 --- a/Discovery/DiscoveryTests/Presentation/SearchViewModelTests.swift +++ b/Discovery/DiscoveryTests/Presentation/SearchViewModelTests.swift @@ -44,7 +44,6 @@ final class SearchViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "123", - certificate: nil, numPages: 2, coursesCount: 0), CourseItem(name: "Test2", @@ -57,7 +56,6 @@ final class SearchViewModelTests: XCTestCase { enrollmentStart: Date(), enrollmentEnd: Date(), courseID: "1243", - certificate: nil, numPages: 1, coursesCount: 0) ] diff --git a/Discussion/Discussion/Data/Model/Data_CreatedComment.swift b/Discussion/Discussion/Data/Model/Data_CreatedComment.swift index b613e0cc9..174f91753 100644 --- a/Discussion/Discussion/Data/Model/Data_CreatedComment.swift +++ b/Discussion/Discussion/Data/Model/Data_CreatedComment.swift @@ -31,6 +31,7 @@ public extension DataLayer { public let childCount: Int public let children: [String] public let abuseFlaggedAnyUser: String? + public let profileImage: ProfileImage enum CodingKeys: String, CodingKey { case id = "id" @@ -54,6 +55,7 @@ public extension DataLayer { case childCount = "child_count" case children = "children" case abuseFlaggedAnyUser = "abuse_flagged_any_user" + case profileImage = "profile_image" } } } @@ -61,7 +63,7 @@ public extension DataLayer { public extension DataLayer.CreatedComment { var domain: Post { Post(authorName: author ?? DiscussionLocalization.anonymous, - authorAvatar: "", + authorAvatar: profileImage.imageURLSmall?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "", postDate: Date(iso8601: createdAt), postTitle: "", postBodyHtml: renderedBody, diff --git a/Discussion/Discussion/Data/Network/DiscussionEndpoint.swift b/Discussion/Discussion/Data/Network/DiscussionEndpoint.swift index 4166c3ba2..2c75a8afa 100644 --- a/Discussion/Discussion/Data/Network/DiscussionEndpoint.swift +++ b/Discussion/Discussion/Data/Network/DiscussionEndpoint.swift @@ -11,7 +11,7 @@ import Alamofire enum DiscussionEndpoint: EndPointType { case getCourseDiscussionInfo(courseID: String) - case getThreads(courseID: String, type: ThreadType, page: Int) + case getThreads(courseID: String, type: ThreadType, filter: ThreadsFilter, page: Int) case getTopics(courseID: String) case getDiscussionComments(threadID: String, page: Int) case getQuestionComments(threadID: String, page: Int) @@ -41,7 +41,7 @@ enum DiscussionEndpoint: EndPointType { case let .getCommentResponses(commentID, _): return "/api/discussion/v1/comments/\(commentID)" case .addCommentTo: - return "/api/discussion/v1/comments/" + return "/mobile_api_extensions/discussion/v1/comments/" case let .voteThread(_, threadID): return "/api/discussion/v1/threads/\(threadID)/" case let .voteResponse(_, responseID): @@ -118,7 +118,7 @@ enum DiscussionEndpoint: EndPointType { switch self { case .getCourseDiscussionInfo: return .requestParameters(encoding: URLEncoding.queryString) - case let .getThreads(courseID, type, page): + case let .getThreads(courseID, type, filter, page): var parameters: [String: Encodable] switch type { case .allPosts: @@ -149,6 +149,14 @@ enum DiscussionEndpoint: EndPointType { "page": page ] } + switch filter { + case .allThreads: + break + case .unread: + parameters["view"] = "unread" + case .unanswered: + parameters["view"] = "unanswered" + } return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) case .getTopics: return .requestParameters(encoding: URLEncoding.queryString) diff --git a/Discussion/Discussion/Data/Network/DiscussionRepository.swift b/Discussion/Discussion/Data/Network/DiscussionRepository.swift index 418bf087f..c04e3f8d5 100644 --- a/Discussion/Discussion/Data/Network/DiscussionRepository.swift +++ b/Discussion/Discussion/Data/Network/DiscussionRepository.swift @@ -10,7 +10,7 @@ import Core import Combine public protocol DiscussionRepositoryProtocol { - func getThreads(courseID: String, type: ThreadType, page: Int) async throws -> ThreadLists + func getThreads(courseID: String, type: ThreadType, filter: ThreadsFilter, page: Int) async throws -> ThreadLists func searchThreads(courseID: String, searchText: String, pageNumber: Int) async throws -> ThreadLists func getTopics(courseID: String) async throws -> Topics func getDiscussionComments(threadID: String, page: Int) async throws -> ([UserComment], Int) @@ -43,9 +43,9 @@ public class DiscussionRepository: DiscussionRepositoryProtocol { self.router = router } - public func getThreads(courseID: String, type: ThreadType, page: Int) async throws -> ThreadLists { + public func getThreads(courseID: String, type: ThreadType, filter: ThreadsFilter, page: Int) async throws -> ThreadLists { let threads = try await api.requestData(DiscussionEndpoint - .getThreads(courseID: courseID, type: type, page: page)) + .getThreads(courseID: courseID, type: type, filter: filter, page: page)) return try await renameThreadUser(data: threads).domain } @@ -205,7 +205,7 @@ public class DiscussionRepositoryMock: DiscussionRepositoryProtocol { abuseFlagged: false) ] - public func getThreads(courseID: String, type: ThreadType, page: Int) async throws -> ThreadLists { + public func getThreads(courseID: String, type: ThreadType, filter: ThreadsFilter, page: Int) async throws -> ThreadLists { ThreadLists( threads: [ UserThread(id: "", author: "Peter", diff --git a/Discussion/Discussion/Domain/DiscussionInteractor.swift b/Discussion/Discussion/Domain/DiscussionInteractor.swift index 948691558..28d0c225a 100644 --- a/Discussion/Discussion/Domain/DiscussionInteractor.swift +++ b/Discussion/Discussion/Domain/DiscussionInteractor.swift @@ -10,7 +10,7 @@ import Core //sourcery: AutoMockable public protocol DiscussionInteractorProtocol { - func getThreadsList(courseID: String, type: ThreadType, page: Int) async throws -> ThreadLists + func getThreadsList(courseID: String, type: ThreadType, filter: ThreadsFilter, page: Int) async throws -> ThreadLists func getTopics(courseID: String) async throws -> Topics func searchThreads(courseID: String, searchText: String, pageNumber: Int) async throws -> ThreadLists func getDiscussionComments(threadID: String, page: Int) async throws -> ([UserComment], Int) @@ -34,8 +34,8 @@ public class DiscussionInteractor: DiscussionInteractorProtocol { self.repository = repository } - public func getThreadsList(courseID: String, type: ThreadType, page: Int) async throws -> ThreadLists { - return try await repository.getThreads(courseID: courseID, type: type, page: page) + public func getThreadsList(courseID: String, type: ThreadType, filter: ThreadsFilter, page: Int) async throws -> ThreadLists { + return try await repository.getThreads(courseID: courseID, type: type, filter: filter, page: page) } public func searchThreads(courseID: String, searchText: String, pageNumber: Int) async throws -> ThreadLists { diff --git a/Discussion/Discussion/Domain/Model/ThreadType.swift b/Discussion/Discussion/Domain/Model/ThreadType.swift index 01d1db5f8..756f234c0 100644 --- a/Discussion/Discussion/Domain/Model/ThreadType.swift +++ b/Discussion/Discussion/Domain/Model/ThreadType.swift @@ -13,3 +13,9 @@ public enum ThreadType { case nonCourseTopics case courseTopics(topicID: String) } + +public enum ThreadsFilter { + case allThreads + case unread + case unanswered +} diff --git a/Discussion/Discussion/Presentation/Posts/PostsView.swift b/Discussion/Discussion/Presentation/Posts/PostsView.swift index c8186e615..ab8b368b7 100644 --- a/Discussion/Discussion/Presentation/Posts/PostsView.swift +++ b/Discussion/Discussion/Presentation/Posts/PostsView.swift @@ -27,10 +27,11 @@ public struct PostsView: View { self.router = router self.showTopMenu = showTopMenu self.viewModel = viewModel + self.viewModel.courseID = courseID self.viewModel.topics = topics viewModel.type = type Task { - await viewModel.getPostsPagination(courseID: courseID) + await viewModel.getPosts(courseID: courseID, pageNumber: 1, withProgress: true) } } @@ -40,9 +41,10 @@ public struct PostsView: View { self.router = router self.viewModel = viewModel Task { - await viewModel.getPostsPagination(courseID: courseID) + await viewModel.getPosts(courseID: courseID, pageNumber: 1, withProgress: true) } self.showTopMenu = true + self.viewModel.courseID = courseID } public var body: some View { @@ -94,13 +96,15 @@ public struct PostsView: View { }.frameLimit() RefreshableScrollViewCompat(action: { listAnimation = nil - _ = await viewModel.getPostsPagination(courseID: courseID, - withProgress: isIOS14) + viewModel.resetPosts() + _ = await viewModel.getPosts(courseID: courseID, + pageNumber: 1, + withProgress: isIOS14) }) { VStack { VStack {}.frame(height: 1) .id(1) - let posts = viewModel.filteredPosts + let posts = Array(viewModel.filteredPosts.enumerated()) HStack { Text(title) .font(Theme.Fonts.titleLarge) @@ -109,9 +113,14 @@ public struct PostsView: View { .padding(.top, 12) Spacer() } - ForEach(posts, id: \.id) { post in + ForEach(posts, id: \.offset) { index, post in PostCell(post: post).padding(24) - if posts.last != post { + .onAppear { + Task { + await viewModel.getPostsPagination(courseID: self.courseID, index: index) + } + } + if posts.last?.element != post { Divider().padding(.horizontal, 24) } } @@ -178,7 +187,11 @@ public struct PostsView: View { @MainActor private func reloadPage(onSuccess: @escaping () -> Void) { Task { - guard await viewModel.getPostsPagination(courseID: courseID) else { return } + listAnimation = nil + viewModel.resetPosts() + _ = await viewModel.getPosts(courseID: courseID, + pageNumber: 1, + withProgress: isIOS14) onSuccess() } } diff --git a/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift b/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift index 515e79ec1..ac5572046 100644 --- a/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift +++ b/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift @@ -10,8 +10,26 @@ import SwiftUI import Combine import Core +public extension ThreadsFilter { + + var localizedValue: String { + switch self { + case .allThreads: + return DiscussionLocalization.Posts.Filter.allPosts + case .unread: + return DiscussionLocalization.Posts.Filter.unread + case .unanswered: + return DiscussionLocalization.Posts.Filter.unanswered + } + } +} + public class PostsViewModel: ObservableObject { + public var nextPage = 1 + public var totalPages = 1 + public private(set) var fetchInProgress = false + public enum ButtonType { case sort case filter @@ -34,31 +52,25 @@ public class PostsViewModel: ObservableObject { } } - public enum FilterType { - case allPosts - case unread - case unanswered - - var localizedValue: String { - switch self { - case .allPosts: - return DiscussionLocalization.Posts.Filter.allPosts - case .unread: - return DiscussionLocalization.Posts.Filter.unread - case .unanswered: - return DiscussionLocalization.Posts.Filter.unanswered - } - } - } - @Published private(set) var isShowProgress = false @Published var showError: Bool = false @Published var filteredPosts: [DiscussionPost] = [] - @Published var filterTitle: FilterType = .allPosts + @Published var filterTitle: ThreadsFilter = .allThreads { + willSet { + if let courseID { + resetPosts() + Task { + _ = await getPosts(courseID: courseID, pageNumber: 1) + } + } + } + } @Published var sortTitle: SortType = .recentActivity @Published var filterButtons: [ActionSheet.Button] = [] + public var courseID: String? + var errorMessage: String? { didSet { withAnimation { @@ -103,6 +115,14 @@ public class PostsViewModel: ObservableObject { }) } + public func resetPosts() { + filteredPosts = [] + discussionPosts = [] + threads.threads = [] + nextPage = 1 + totalPages = 1 + } + public func generateButtons(type: ButtonType) { switch type { case .sort: @@ -124,7 +144,7 @@ public class PostsViewModel: ObservableObject { case .filter: self.filterButtons = [ ActionSheet.Button.default(Text(DiscussionLocalization.Posts.Filter.allPosts)) { - self.filterTitle = .allPosts + self.filterTitle = .allThreads self.sortPosts() }, ActionSheet.Button.default(Text(DiscussionLocalization.Posts.Filter.unread)) { @@ -155,18 +175,21 @@ public class PostsViewModel: ObservableObject { } @MainActor - func getPostsPagination(courseID: String, withProgress: Bool = true) async -> Bool { - self.threads.threads = [] - guard await getPosts(courseID: courseID, pageNumber: 1, withProgress: withProgress) else { return false } - if !threads.threads.isEmpty { - if threads.threads[0].numPages > 1 { - for i in 2...threads.threads[0].numPages { - guard await getPosts(courseID: courseID, pageNumber: i, withProgress: withProgress) else { return false } + func getPostsPagination(courseID: String, index: Int, withProgress: Bool = true) async { + print(">>>>>> INDEX", index) + if !fetchInProgress { + if totalPages > 1 { + if index == threads.threads.count - 3 { + if totalPages != 1 { + if nextPage <= totalPages { + _ = await getPosts(courseID: courseID, + pageNumber: self.nextPage, + withProgress: withProgress) + } + } } } } - - return true } @MainActor @@ -178,22 +201,34 @@ public class PostsViewModel: ObservableObject { threads.threads += try await interactor .getThreadsList(courseID: courseID, type: .allPosts, + filter: filterTitle, page: pageNumber).threads + self.totalPages = threads.threads[0].numPages + self.nextPage += 1 case .followingPosts: threads.threads += try await interactor .getThreadsList(courseID: courseID, type: .followingPosts, + filter: filterTitle, page: pageNumber).threads + self.totalPages = threads.threads[0].numPages + self.nextPage += 1 case .nonCourseTopics: threads.threads += try await interactor .getThreadsList(courseID: courseID, type: .nonCourseTopics, + filter: filterTitle, page: pageNumber).threads + self.totalPages = threads.threads[0].numPages + self.nextPage += 1 case .courseTopics(topicID: let topicID): threads.threads += try await interactor .getThreadsList(courseID: courseID, type: .courseTopics(topicID: topicID), + filter: filterTitle, page: pageNumber).threads + self.totalPages = threads.threads[0].numPages + self.nextPage += 1 case .none: isShowProgress = false return false @@ -216,14 +251,6 @@ public class PostsViewModel: ObservableObject { private func sortPosts() { self.filteredPosts = self.discussionPosts - switch filterTitle { - case .allPosts: - break - case .unread: - self.filteredPosts = self.filteredPosts.filter({ $0.unreadCommentCount > 0 }) - case .unanswered: - self.filteredPosts = self.filteredPosts.filter({ $0.type == .question && !$0.hasEndorsed }) - } switch sortTitle { case .recentActivity: self.filteredPosts = self.filteredPosts.sorted(by: { $0.lastPostDate > $1.lastPostDate }) diff --git a/Discussion/DiscussionTests/DiscussionMock.generated.swift b/Discussion/DiscussionTests/DiscussionMock.generated.swift index dffe613e9..f08d3bc5a 100644 --- a/Discussion/DiscussionTests/DiscussionMock.generated.swift +++ b/Discussion/DiscussionTests/DiscussionMock.generated.swift @@ -502,6 +502,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?() } + open func showForgotPasswordScreen() { + addInvocation(.m_showForgotPasswordScreen) + let perform = methodPerformValue(.m_showForgotPasswordScreen) as? () -> Void + perform?() + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -536,6 +542,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showMainScreen case m_showLoginScreen case m_showRegisterScreen + case m_showForgotPasswordScreen case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) @@ -571,6 +578,8 @@ open class BaseRouterMock: BaseRouter, Mock { case (.m_showRegisterScreen, .m_showRegisterScreen): return .match + case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsOktapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsOktapped, let rhsType)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) @@ -616,6 +625,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 + case .m_showForgotPasswordScreen: return 0 case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_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_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue @@ -632,6 +642,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return ".showMainScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" + case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped: return ".presentAlert(alertTitle:alertMessage:action:image:onCloseTapped:okTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" @@ -662,6 +673,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} + public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(`alertTitle`, `alertMessage`, `action`, `image`, `onCloseTapped`, `okTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} @@ -696,6 +708,9 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showRegisterScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showRegisterScreen, performs: perform) } + public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showForgotPasswordScreen, performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: 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_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) } @@ -828,10 +843,10 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { } private var __p_isInternetAvaliable: (Bool)? - public var isWifi: Bool { - get { invocations.append(.p_isWifi_get); return __p_isWifi ?? givenGetterValue(.p_isWifi_get, "ConnectivityProtocolMock - stub value for isWifi was not defined") } + 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_isWifi: (Bool)? + 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") } @@ -845,12 +860,12 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate enum MethodType { case p_isInternetAvaliable_get - case p_isWifi_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_isWifi_get,.p_isWifi_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 } @@ -859,14 +874,14 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { func intValue() -> Int { switch self { case .p_isInternetAvaliable_get: return 0 - case .p_isWifi_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_isWifi_get: return "[get] .isWifi" + case .p_isMobileData_get: return "[get] .isMobileData" case .p_internetReachableSubject_get: return "[get] .internetReachableSubject" } } @@ -883,8 +898,8 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { 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 isWifi(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_isWifi_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) })) @@ -896,7 +911,7 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate var method: MethodType public static var isInternetAvaliable: Verify { return Verify(method: .p_isInternetAvaliable_get) } - public static var isWifi: Verify { return Verify(method: .p_isWifi_get) } + public static var isMobileData: Verify { return Verify(method: .p_isMobileData_get) } public static var internetReachableSubject: Verify { return Verify(method: .p_internetReachableSubject_get) } } @@ -1023,16 +1038,16 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock - open func getThreadsList(courseID: String, type: ThreadType, page: Int) throws -> ThreadLists { - addInvocation(.m_getThreadsList__courseID_courseIDtype_typepage_page(Parameter.value(`courseID`), Parameter.value(`type`), Parameter.value(`page`))) - let perform = methodPerformValue(.m_getThreadsList__courseID_courseIDtype_typepage_page(Parameter.value(`courseID`), Parameter.value(`type`), Parameter.value(`page`))) as? (String, ThreadType, Int) -> Void - perform?(`courseID`, `type`, `page`) + open func getThreadsList(courseID: String, type: ThreadType, filter: ThreadsFilter, page: Int) throws -> ThreadLists { + addInvocation(.m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(Parameter.value(`courseID`), Parameter.value(`type`), Parameter.value(`filter`), Parameter.value(`page`))) + let perform = methodPerformValue(.m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(Parameter.value(`courseID`), Parameter.value(`type`), Parameter.value(`filter`), Parameter.value(`page`))) as? (String, ThreadType, ThreadsFilter, Int) -> Void + perform?(`courseID`, `type`, `filter`, `page`) var __value: ThreadLists do { - __value = try methodReturnValue(.m_getThreadsList__courseID_courseIDtype_typepage_page(Parameter.value(`courseID`), Parameter.value(`type`), Parameter.value(`page`))).casted() + __value = try methodReturnValue(.m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(Parameter.value(`courseID`), Parameter.value(`type`), Parameter.value(`filter`), Parameter.value(`page`))).casted() } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for getThreadsList(courseID: String, type: ThreadType, page: Int). Use given") - Failure("Stub return value not specified for getThreadsList(courseID: String, type: ThreadType, page: Int). Use given") + onFatalFailure("Stub return value not specified for getThreadsList(courseID: String, type: ThreadType, filter: ThreadsFilter, page: Int). Use given") + Failure("Stub return value not specified for getThreadsList(courseID: String, type: ThreadType, filter: ThreadsFilter, page: Int). Use given") } catch { throw error } @@ -1228,7 +1243,7 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock fileprivate enum MethodType { - case m_getThreadsList__courseID_courseIDtype_typepage_page(Parameter, Parameter, Parameter) + case m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(Parameter, Parameter, Parameter, Parameter) case m_getTopics__courseID_courseID(Parameter) case m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(Parameter, Parameter, Parameter) case m_getDiscussionComments__threadID_threadIDpage_page(Parameter, Parameter) @@ -1245,10 +1260,11 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { - case (.m_getThreadsList__courseID_courseIDtype_typepage_page(let lhsCourseid, let lhsType, let lhsPage), .m_getThreadsList__courseID_courseIDtype_typepage_page(let rhsCourseid, let rhsType, let rhsPage)): + case (.m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(let lhsCourseid, let lhsType, let lhsFilter, let lhsPage), .m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(let rhsCourseid, let rhsType, let rhsFilter, let rhsPage)): 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: lhsType, rhs: rhsType, with: matcher), lhsType, rhsType, "type")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFilter, rhs: rhsFilter, with: matcher), lhsFilter, rhsFilter, "filter")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPage, rhs: rhsPage, with: matcher), lhsPage, rhsPage, "page")) return Matcher.ComparisonResult(results) @@ -1334,7 +1350,7 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock func intValue() -> Int { switch self { - case let .m_getThreadsList__courseID_courseIDtype_typepage_page(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue + case let .m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(p0, p1, p2, p3): return p0.intValue + p1.intValue + p2.intValue + p3.intValue case let .m_getTopics__courseID_courseID(p0): return p0.intValue case let .m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue case let .m_getDiscussionComments__threadID_threadIDpage_page(p0, p1): return p0.intValue + p1.intValue @@ -1352,7 +1368,7 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock } func assertionName() -> String { switch self { - case .m_getThreadsList__courseID_courseIDtype_typepage_page: return ".getThreadsList(courseID:type:page:)" + case .m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page: return ".getThreadsList(courseID:type:filter:page:)" case .m_getTopics__courseID_courseID: return ".getTopics(courseID:)" case .m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber: return ".searchThreads(courseID:searchText:pageNumber:)" case .m_getDiscussionComments__threadID_threadIDpage_page: return ".getDiscussionComments(threadID:page:)" @@ -1379,8 +1395,8 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock } - public static func getThreadsList(courseID: Parameter, type: Parameter, page: Parameter, willReturn: ThreadLists...) -> MethodStub { - return Given(method: .m_getThreadsList__courseID_courseIDtype_typepage_page(`courseID`, `type`, `page`), products: willReturn.map({ StubProduct.return($0 as Any) })) + public static func getThreadsList(courseID: Parameter, type: Parameter, filter: Parameter, page: Parameter, willReturn: ThreadLists...) -> MethodStub { + return Given(method: .m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(`courseID`, `type`, `filter`, `page`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func getTopics(courseID: Parameter, willReturn: Topics...) -> MethodStub { return Given(method: .m_getTopics__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) @@ -1400,12 +1416,12 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock public static func addCommentTo(threadID: Parameter, rawBody: Parameter, parentID: Parameter, willReturn: Post...) -> MethodStub { return Given(method: .m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID(`threadID`, `rawBody`, `parentID`), products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func getThreadsList(courseID: Parameter, type: Parameter, page: Parameter, willThrow: Error...) -> MethodStub { - return Given(method: .m_getThreadsList__courseID_courseIDtype_typepage_page(`courseID`, `type`, `page`), products: willThrow.map({ StubProduct.throw($0) })) + public static func getThreadsList(courseID: Parameter, type: Parameter, filter: Parameter, page: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(`courseID`, `type`, `filter`, `page`), products: willThrow.map({ StubProduct.throw($0) })) } - public static func getThreadsList(courseID: Parameter, type: Parameter, page: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + public static func getThreadsList(courseID: Parameter, type: Parameter, filter: Parameter, page: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_getThreadsList__courseID_courseIDtype_typepage_page(`courseID`, `type`, `page`), products: willThrow.map({ StubProduct.throw($0) })) }() + let given: Given = { return Given(method: .m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(`courseID`, `type`, `filter`, `page`), products: willThrow.map({ StubProduct.throw($0) })) }() let stubber = given.stubThrows(for: (ThreadLists).self) willProduce(stubber) return given @@ -1545,7 +1561,7 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock public struct Verify { fileprivate var method: MethodType - public static func getThreadsList(courseID: Parameter, type: Parameter, page: Parameter) -> Verify { return Verify(method: .m_getThreadsList__courseID_courseIDtype_typepage_page(`courseID`, `type`, `page`))} + public static func getThreadsList(courseID: Parameter, type: Parameter, filter: Parameter, page: Parameter) -> Verify { return Verify(method: .m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(`courseID`, `type`, `filter`, `page`))} public static func getTopics(courseID: Parameter) -> Verify { return Verify(method: .m_getTopics__courseID_courseID(`courseID`))} public static func searchThreads(courseID: Parameter, searchText: Parameter, pageNumber: Parameter) -> Verify { return Verify(method: .m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(`courseID`, `searchText`, `pageNumber`))} public static func getDiscussionComments(threadID: Parameter, page: Parameter) -> Verify { return Verify(method: .m_getDiscussionComments__threadID_threadIDpage_page(`threadID`, `page`))} @@ -1565,8 +1581,8 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock fileprivate var method: MethodType var performs: Any - public static func getThreadsList(courseID: Parameter, type: Parameter, page: Parameter, perform: @escaping (String, ThreadType, Int) -> Void) -> Perform { - return Perform(method: .m_getThreadsList__courseID_courseIDtype_typepage_page(`courseID`, `type`, `page`), performs: perform) + public static func getThreadsList(courseID: Parameter, type: Parameter, filter: Parameter, page: Parameter, perform: @escaping (String, ThreadType, ThreadsFilter, Int) -> Void) -> Perform { + return Perform(method: .m_getThreadsList__courseID_courseIDtype_typefilter_filterpage_page(`courseID`, `type`, `filter`, `page`), performs: perform) } public static func getTopics(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getTopics__courseID_courseID(`courseID`), performs: perform) @@ -1804,6 +1820,12 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { perform?() } + open func showForgotPasswordScreen() { + addInvocation(.m_showForgotPasswordScreen) + let perform = methodPerformValue(.m_showForgotPasswordScreen) as? () -> Void + perform?() + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -1843,6 +1865,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case m_showMainScreen case m_showLoginScreen case m_showRegisterScreen + case m_showForgotPasswordScreen case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) @@ -1911,6 +1934,8 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case (.m_showRegisterScreen, .m_showRegisterScreen): return .match + case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsOktapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsOktapped, let rhsType)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) @@ -1961,6 +1986,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case .m_showMainScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 + case .m_showForgotPasswordScreen: return 0 case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_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_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue @@ -1982,6 +2008,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case .m_showMainScreen: return ".showMainScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" + case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped: return ".presentAlert(alertTitle:alertMessage:action:image:onCloseTapped:okTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" @@ -2017,6 +2044,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} + public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(`alertTitle`, `alertMessage`, `action`, `image`, `onCloseTapped`, `okTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} @@ -2066,6 +2094,9 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { public static func showRegisterScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showRegisterScreen, performs: perform) } + public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showForgotPasswordScreen, performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: 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_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) } diff --git a/Discussion/DiscussionTests/Presentation/Posts/PostViewModelTests.swift b/Discussion/DiscussionTests/Presentation/Posts/PostViewModelTests.swift index 77a0984e1..3250b8d9d 100644 --- a/Discussion/DiscussionTests/Presentation/Posts/PostViewModelTests.swift +++ b/Discussion/DiscussionTests/Presentation/Posts/PostViewModelTests.swift @@ -106,33 +106,33 @@ final class PostViewModelTests: XCTestCase { viewModel.type = .allPosts - Given(interactor, .getThreadsList(courseID: .any, type: .any, page: .any, willReturn: threads)) + Given(interactor, .getThreadsList(courseID: .any, type: .any, filter: .any, page: .any, willReturn: threads)) viewModel.type = .allPosts - result = await viewModel.getPostsPagination(courseID: "1") + result = await viewModel.getPosts(courseID: "1", pageNumber: 1) XCTAssertTrue(result) result = false viewModel.type = .courseTopics(topicID: "") - result = await viewModel.getPostsPagination(courseID: "1") + result = await viewModel.getPosts(courseID: "1", pageNumber: 1) XCTAssertTrue(result) result = false viewModel.type = .followingPosts - result = await viewModel.getPostsPagination(courseID: "1") + result = await viewModel.getPosts(courseID: "1", pageNumber: 1) XCTAssertTrue(result) result = false viewModel.type = .nonCourseTopics - result = await viewModel.getPostsPagination(courseID: "1") + result = await viewModel.getPosts(courseID: "1", pageNumber: 1) XCTAssertTrue(result) result = false viewModel.type = .none - result = await viewModel.getPostsPagination(courseID: "1") + result = await viewModel.getPosts(courseID: "1", pageNumber: 1) XCTAssertFalse(result) - Verify(interactor, 4, .getThreadsList(courseID: .value("1"), type: .any, page: .value(1))) + Verify(interactor, 4, .getThreadsList(courseID: .value("1"), type: .any, filter: .any, page: .value(1))) XCTAssertFalse(viewModel.isShowProgress) XCTAssertFalse(viewModel.showError) @@ -148,12 +148,12 @@ final class PostViewModelTests: XCTestCase { let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) - Given(interactor, .getThreadsList(courseID: .any, type: .any, page: .any, willThrow: noInternetError)) + Given(interactor, .getThreadsList(courseID: .any, type: .any, filter: .any, page: .any, willThrow: noInternetError)) viewModel.type = .allPosts - result = await viewModel.getPostsPagination(courseID: "1") + result = await viewModel.getPosts(courseID: "1", pageNumber: 1) - Verify(interactor, 1, .getThreadsList(courseID: .any, type: .any, page: .any)) + Verify(interactor, 1, .getThreadsList(courseID: .any, type: .any, filter: .any, page: .any)) XCTAssertFalse(result) XCTAssertFalse(viewModel.isShowProgress) @@ -168,12 +168,12 @@ final class PostViewModelTests: XCTestCase { var result = false let viewModel = PostsViewModel(interactor: interactor, router: router, config: config) - Given(interactor, .getThreadsList(courseID: .any, type: .any, page: .any, willThrow: NSError())) + Given(interactor, .getThreadsList(courseID: .any, type: .any, filter: .any, page: .any, willThrow: NSError())) viewModel.type = .allPosts - result = await viewModel.getPostsPagination(courseID: "1") + result = await viewModel.getPosts(courseID: "1", pageNumber: 1) - Verify(interactor, 1, .getThreadsList(courseID: .any, type: .any, page: .any)) + Verify(interactor, 1, .getThreadsList(courseID: .any, type: .any, filter: .any, page: .any)) XCTAssertFalse(result) XCTAssertFalse(viewModel.isShowProgress) @@ -187,28 +187,26 @@ final class PostViewModelTests: XCTestCase { let config = ConfigMock() let viewModel = PostsViewModel(interactor: interactor, router: router, config: config) + Given(interactor, .getThreadsList(courseID: .any, type: .any, filter: .any, page: .any, + willReturn: threads)) viewModel.type = .allPosts - Given(interactor, .getThreadsList(courseID: .any, type: .any, page: .any, willReturn: threads)) - - viewModel.type = .allPosts - viewModel.filterTitle = .allPosts viewModel.sortTitle = .mostActivity - _ = await viewModel.getPostsPagination(courseID: "1") + _ = await viewModel.getPosts(courseID: "1", pageNumber: 1) XCTAssertTrue(viewModel.filteredPosts[0].title == "4") viewModel.filterTitle = .unread viewModel.sortTitle = .recentActivity - _ = await viewModel.getPostsPagination(courseID: "1") + _ = await viewModel.getPosts(courseID: "1", pageNumber: 1) XCTAssertTrue(viewModel.filteredPosts[0].title == "2") - XCTAssertNil(viewModel.filteredPosts.first(where: {$0.unreadCommentCount == 0})) + XCTAssertNotNil(viewModel.filteredPosts.first(where: {$0.unreadCommentCount == 4})) viewModel.filterTitle = .unanswered viewModel.sortTitle = .mostVotes - _ = await viewModel.getPostsPagination(courseID: "1") + _ = await viewModel.getPosts(courseID: "1", pageNumber: 1) XCTAssertTrue(viewModel.filteredPosts[0].title == "3") - XCTAssertNil(viewModel.filteredPosts.first(where: { $0.hasEndorsed })) + XCTAssertNotNil(viewModel.filteredPosts.first(where: { $0.hasEndorsed })) - Verify(interactor, .getThreadsList(courseID: .any, type: .any, page: .any)) + Verify(interactor, .getThreadsList(courseID: .any, type: .any, filter: .any, page: .any)) } } diff --git a/NewEdX/Router.swift b/NewEdX/Router.swift index da3cb8e12..fd9cb0d61 100644 --- a/NewEdX/Router.swift +++ b/NewEdX/Router.swift @@ -172,17 +172,13 @@ public class Router: AuthorizationRouter, DiscoveryRouter, ProfileRouter, Dashbo courseEnd: Date?, enrollmentStart: Date?, enrollmentEnd: Date?, - title: String, - courseBanner: String, - certificate: Certificate?) { + title: String) { let screensView = CourseContainerView( viewModel: Container.shared.resolve(CourseContainerViewModel.self, arguments: isActive, courseStart, courseEnd, enrollmentStart, enrollmentEnd)!, courseID: courseID, - title: title, - courseBanner: courseBanner, - certificate: certificate + title: title ) let controller = SwiftUIHostController(view: screensView) diff --git a/Profile/ProfileTests/ProfileMock.generated.swift b/Profile/ProfileTests/ProfileMock.generated.swift index 018f90e98..c41a29771 100644 --- a/Profile/ProfileTests/ProfileMock.generated.swift +++ b/Profile/ProfileTests/ProfileMock.generated.swift @@ -502,6 +502,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?() } + open func showForgotPasswordScreen() { + addInvocation(.m_showForgotPasswordScreen) + let perform = methodPerformValue(.m_showForgotPasswordScreen) as? () -> Void + perform?() + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -536,6 +542,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showMainScreen case m_showLoginScreen case m_showRegisterScreen + case m_showForgotPasswordScreen case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) @@ -571,6 +578,8 @@ open class BaseRouterMock: BaseRouter, Mock { case (.m_showRegisterScreen, .m_showRegisterScreen): return .match + case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsOktapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsOktapped, let rhsType)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) @@ -616,6 +625,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 + case .m_showForgotPasswordScreen: return 0 case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_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_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue @@ -632,6 +642,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showMainScreen: return ".showMainScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" + case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped: return ".presentAlert(alertTitle:alertMessage:action:image:onCloseTapped:okTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" @@ -662,6 +673,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} + public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(`alertTitle`, `alertMessage`, `action`, `image`, `onCloseTapped`, `okTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} @@ -696,6 +708,9 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showRegisterScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showRegisterScreen, performs: perform) } + public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showForgotPasswordScreen, performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: 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_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) } @@ -828,10 +843,10 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { } private var __p_isInternetAvaliable: (Bool)? - public var isWifi: Bool { - get { invocations.append(.p_isWifi_get); return __p_isWifi ?? givenGetterValue(.p_isWifi_get, "ConnectivityProtocolMock - stub value for isWifi was not defined") } + 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_isWifi: (Bool)? + 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") } @@ -845,12 +860,12 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate enum MethodType { case p_isInternetAvaliable_get - case p_isWifi_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_isWifi_get,.p_isWifi_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 } @@ -859,14 +874,14 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { func intValue() -> Int { switch self { case .p_isInternetAvaliable_get: return 0 - case .p_isWifi_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_isWifi_get: return "[get] .isWifi" + case .p_isMobileData_get: return "[get] .isMobileData" case .p_internetReachableSubject_get: return "[get] .internetReachableSubject" } } @@ -883,8 +898,8 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { 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 isWifi(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_isWifi_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) })) @@ -896,7 +911,7 @@ open class ConnectivityProtocolMock: ConnectivityProtocol, Mock { fileprivate var method: MethodType public static var isInternetAvaliable: Verify { return Verify(method: .p_isInternetAvaliable_get) } - public static var isWifi: Verify { return Verify(method: .p_isWifi_get) } + public static var isMobileData: Verify { return Verify(method: .p_isMobileData_get) } public static var internetReachableSubject: Verify { return Verify(method: .p_internetReachableSubject_get) } } @@ -1632,6 +1647,12 @@ open class ProfileRouterMock: ProfileRouter, Mock { perform?() } + open func showForgotPasswordScreen() { + addInvocation(.m_showForgotPasswordScreen) + let perform = methodPerformValue(.m_showForgotPasswordScreen) as? () -> Void + perform?() + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -1670,6 +1691,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case m_showMainScreen case m_showLoginScreen case m_showRegisterScreen + case m_showForgotPasswordScreen case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) @@ -1721,6 +1743,8 @@ open class ProfileRouterMock: ProfileRouter, Mock { case (.m_showRegisterScreen, .m_showRegisterScreen): return .match + case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsOktapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsOktapped, let rhsType)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) @@ -1770,6 +1794,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case .m_showMainScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 + case .m_showForgotPasswordScreen: return 0 case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_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_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue @@ -1790,6 +1815,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case .m_showMainScreen: return ".showMainScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" + case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped: return ".presentAlert(alertTitle:alertMessage:action:image:onCloseTapped:okTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" @@ -1824,6 +1850,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} + public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessageaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTapped(`alertTitle`, `alertMessage`, `action`, `image`, `onCloseTapped`, `okTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} @@ -1870,6 +1897,9 @@ open class ProfileRouterMock: ProfileRouter, Mock { public static func showRegisterScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showRegisterScreen, performs: perform) } + public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showForgotPasswordScreen, performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: 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_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) }