diff --git a/FBSDKShareKit/FBSDKShareKit/UserInterface/MessageDialog.swift b/FBSDKShareKit/FBSDKShareKit/UserInterface/MessageDialog.swift index 1de081f21..e5080aeee 100644 --- a/FBSDKShareKit/FBSDKShareKit/UserInterface/MessageDialog.swift +++ b/FBSDKShareKit/FBSDKShareKit/UserInterface/MessageDialog.swift @@ -241,7 +241,7 @@ public class MessageDialog: NSObject, SharingDialog { // swiftlint:disable:this return shouldUseNativeDialog && internalUtility.isMessengerAppInstalled } - private func handleCompletion(dialogResults: [String: Any], response: BridgeAPIResponse) { + func handleCompletion(dialogResults: [String: Any], response: BridgeAPIResponse) { let completionGesture = dialogResults[ShareBridgeAPI.CompletionGesture.key] as? String let isCancelGesture = (completionGesture == ShareBridgeAPI.CompletionGesture.cancelValue) if isCancelGesture || response.isCancelled { diff --git a/FBSDKShareKit/FBSDKShareKitTests/Bridging/TestBridgeAPIResponse.swift b/FBSDKShareKit/FBSDKShareKitTests/Bridging/TestBridgeAPIResponse.swift new file mode 100644 index 000000000..942d5314b --- /dev/null +++ b/FBSDKShareKit/FBSDKShareKitTests/Bridging/TestBridgeAPIResponse.swift @@ -0,0 +1,18 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBSDKCoreKit + +final class TestBridgeAPIResponse: BridgeAPIResponse { + + var stubbedResponseParameters: [String: Any]? + + override var responseParameters: [String: Any]? { + stubbedResponseParameters ?? super.responseParameters + } +} diff --git a/FBSDKShareKit/FBSDKShareKitTests/UserInterface/MessageDialogTests.swift b/FBSDKShareKit/FBSDKShareKitTests/UserInterface/MessageDialogTests.swift index 1468052e3..955c17c50 100644 --- a/FBSDKShareKit/FBSDKShareKitTests/UserInterface/MessageDialogTests.swift +++ b/FBSDKShareKit/FBSDKShareKitTests/UserInterface/MessageDialogTests.swift @@ -12,13 +12,6 @@ import XCTest final class MessageDialogTests: XCTestCase { - enum Assumptions { - static let contentValidation = """ - Known valid content should pass validation without issue. \ - If this test fails then the criteria for the fixture may no longer be valid - """ - } - // swiftlint:disable implicitly_unwrapped_optional var dialog: MessageDialog! var shareDialogConfiguration: TestShareDialogConfiguration! @@ -161,6 +154,22 @@ final class MessageDialogTests: XCTestCase { ) } + func testCreatingWithFactoryMethod() { + let content = ShareModelTestUtility.linkContent + dialog = MessageDialog.dialog(content: content, delegate: delegate) + + XCTAssertIdentical(dialog.shareContent, content, .factoryCreation) + XCTAssertIdentical(dialog.delegate, delegate, .factoryCreation) + } + + func testCreatingAndShowingWithFactoryMethod() { + let content = ShareModelTestUtility.linkContent + dialog = MessageDialog.show(content: content, delegate: delegate) + + XCTAssertIdentical(dialog.shareContent, content, .factoryCreation) + XCTAssertIdentical(dialog.delegate, delegate, .factoryCreation) + } + func testCanShow() { shareDialogConfiguration.stubbedShouldUseNativeDialog = true internalUtility.isMessengerAppInstalled = true @@ -192,13 +201,13 @@ final class MessageDialogTests: XCTestCase { dialog = MessageDialog() dialog.shareContent = ShareModelTestUtility.linkContent - XCTAssertNoThrow(try dialog.validate(), Assumptions.contentValidation) + XCTAssertNoThrow(try dialog.validate(), .contentValidation) dialog.shareContent = ShareModelTestUtility.photoContentWithImages - XCTAssertNoThrow(try dialog.validate(), Assumptions.contentValidation) + XCTAssertNoThrow(try dialog.validate(), .contentValidation) dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto - XCTAssertNoThrow(try dialog.validate(), Assumptions.contentValidation) + XCTAssertNoThrow(try dialog.validate(), .contentValidation) dialog.shareContent = ShareModelTestUtility.cameraEffectContent XCTAssertNil( @@ -256,6 +265,142 @@ final class MessageDialogTests: XCTestCase { XCTAssertEqual(error.message, "Message dialog does not support ShareCameraEffectContent.", .failureIsHandled) XCTAssertNil(error.underlyingError, .failureIsHandled) } + + func testShowingDialog() throws { + shareDialogConfiguration.stubbedShouldUseNativeDialog = true + shareDialogConfiguration.stubbedShouldUseSafariViewController = true + internalUtility.isMessengerAppInstalled = true + let parameters = ["key": "value"] + TestShareUtility.stubbedBridgeParameters = parameters + let request = TestBridgeAPIRequest() + bridgeAPIRequestFactory.stubbedBridgeAPIRequest = request + let content = ShareModelTestUtility.linkContent + content.pageID = "foo" + dialog.shareContent = content + + XCTAssertTrue(dialog.show(), .showingValidDialog) + + // Getting bridge parameters + XCTAssertIdentical(shareUtility.capturedBridgeParametersShareContent, content, .showingValidDialog) + let options = try XCTUnwrap(shareUtility.capturedBridgeParametersBridgeOptions, .showingValidDialog) + XCTAssertTrue(options.isEmpty, .showingValidDialog) + XCTAssertEqual( + shareUtility.capturedBridgeParametersShouldFailOnDataError, + dialog.shouldFailOnDataError, + .showingValidDialog + ) + + // Creating bridge API request + XCTAssertEqual(bridgeAPIRequestFactory.capturedProtocolType, .native, .showingValidDialog) + XCTAssertEqual(bridgeAPIRequestFactory.capturedScheme, URLScheme.messengerApp.rawValue, .showingValidDialog) + XCTAssertEqual(bridgeAPIRequestFactory.capturedMethodName, ShareBridgeAPI.MethodName.share, .showingValidDialog) + XCTAssertEqual(bridgeAPIRequestFactory.capturedParameters as? [String: String], parameters, .showingValidDialog) + XCTAssertNil(bridgeAPIRequestFactory.capturedUserInfo, .showingValidDialog) + + // Opening bridge API request + XCTAssertIdentical(bridgeAPIRequestOpener.capturedRequest, request, .showingValidDialog) + XCTAssertEqual( + bridgeAPIRequestOpener.capturedUseSafariViewController, + shareDialogConfiguration.stubbedShouldUseSafariViewController, + .showingValidDialog + ) + XCTAssertNil(bridgeAPIRequestOpener.capturedFromViewController, .showingValidDialog) + + // Logging + XCTAssertEqual(eventLogger.logInternalEventName, .shareDialogShow, .showingValidDialog) + let expectedParameters: [AppEvents.ParameterName: String] = [ + .shareContentType: ShareAppEventsParameters.ContentTypeValue.status, + .shareContentUUID: content.shareUUID!, // swiftlint:disable:this force_unwrapping + .shareContentPageID: content.pageID!, // swiftlint:disable:this force_unwrapping + ] + XCTAssertEqual( + eventLogger.logInternalEventParameters as? [AppEvents.ParameterName: String], + expectedParameters, + .showingValidDialog + ) + XCTAssertTrue(eventLogger.logInternalEventIsImplicitlyLogged ?? false, .showingValidDialog) + XCTAssertEqual(eventLogger.logInternalEventAccessToken, TestAccessTokenWallet.current, .showingValidDialog) + + // Completion + let response = TestBridgeAPIResponse(request: request, error: nil) + response.stubbedResponseParameters = parameters + bridgeAPIRequestOpener.capturedCompletionBlock?(response) + XCTAssertEqual(delegate?.sharerDidCompleteResults as? [String: String], parameters, .showingValidDialog) + XCTAssertIdentical(internalUtility.unregisterTransientObjectObject as AnyObject, dialog, .showingValidDialog) + } + + func testHandlingNormalCancellation() { + let request = TestBridgeAPIRequest() + let response = TestBridgeAPIResponse(cancelledWith: request) + + dialog.handleCompletion(dialogResults: [:], response: response) + + XCTAssertEqual(eventLogger.logInternalEventName, .messengerShareDialogResult, .cancellationIsHandled) + XCTAssertEqual( + eventLogger.logInternalEventParameters as? [AppEvents.ParameterName: String], + [.outcome: ShareAppEventsParameters.DialogOutcomeValue.cancelled], + .cancellationIsHandled + ) + XCTAssertTrue(eventLogger.logInternalEventIsImplicitlyLogged ?? false, .cancellationIsHandled) + XCTAssertIdentical(eventLogger.logInternalEventAccessToken, accessTokenWallet.current, .cancellationIsHandled) + XCTAssertIdentical(delegate.sharerDidCancelSharer as AnyObject, dialog, .cancellationIsHandled) + } + + func testHandlingCancellationViaGesture() { + let request = TestBridgeAPIRequest() + let response = TestBridgeAPIResponse(request: request, error: nil) + let results = [ShareBridgeAPI.CompletionGesture.key: ShareBridgeAPI.CompletionGesture.cancelValue] + dialog.handleCompletion(dialogResults: results, response: response) + + XCTAssertEqual(eventLogger.logInternalEventName, .messengerShareDialogResult, .cancellationIsHandled) + XCTAssertEqual( + eventLogger.logInternalEventParameters as? [AppEvents.ParameterName: String], + [.outcome: ShareAppEventsParameters.DialogOutcomeValue.cancelled], + .cancellationIsHandled + ) + XCTAssertTrue(eventLogger.logInternalEventIsImplicitlyLogged ?? false, .cancellationIsHandled) + XCTAssertIdentical(eventLogger.logInternalEventAccessToken, accessTokenWallet.current, .cancellationIsHandled) + XCTAssertIdentical(delegate.sharerDidCancelSharer as AnyObject, dialog, .cancellationIsHandled) + } + + func testHandlingFailure() { + let request = TestBridgeAPIRequest() + let error = SampleError() + let response = TestBridgeAPIResponse(request: request, error: error) + dialog.handleCompletion(dialogResults: [:], response: response) + + XCTAssertEqual(eventLogger.logInternalEventName, .shareDialogResult, .failureIsHandled) + XCTAssertEqual( + eventLogger.logInternalEventParameters as? [AppEvents.ParameterName: String], + [ + .outcome: ShareAppEventsParameters.DialogOutcomeValue.failed, + .errorMessage: String(describing: error), + ], + .failureIsHandled + ) + XCTAssertTrue(eventLogger.logInternalEventIsImplicitlyLogged ?? false, .failureIsHandled) + XCTAssertIdentical(eventLogger.logInternalEventAccessToken, accessTokenWallet.current, .failureIsHandled) + XCTAssertIdentical(delegate.sharerDidFailSharer as AnyObject, dialog, .failureIsHandled) + XCTAssertEqual(delegate.sharerDidFailError as? SampleError, error, .failureIsHandled) + } + + func testHandlingSuccess() { + let request = TestBridgeAPIRequest() + let response = TestBridgeAPIResponse(request: request, error: nil) + let results = ["key": "value"] + dialog.handleCompletion(dialogResults: results, response: response) + + XCTAssertEqual(eventLogger.logInternalEventName, .messengerShareDialogResult, .successIsHandled) + XCTAssertEqual( + eventLogger.logInternalEventParameters as? [AppEvents.ParameterName: String], + [.outcome: ShareAppEventsParameters.DialogOutcomeValue.completed], + .successIsHandled + ) + XCTAssertTrue(eventLogger.logInternalEventIsImplicitlyLogged ?? false, .successIsHandled) + XCTAssertIdentical(eventLogger.logInternalEventAccessToken, accessTokenWallet.current, .successIsHandled) + XCTAssertIdentical(delegate.sharerDidCompleteSharer as AnyObject, dialog, .successIsHandled) + XCTAssertEqual(delegate.sharerDidCompleteResults as? [String: String], results, .successIsHandled) + } } // MARK: - Assumptions @@ -269,5 +414,15 @@ fileprivate extension String { "The MessageDialog type uses a custom \(type) dependency when provided" } + static let factoryCreation = "A dialog can be created using a factory method" + + static let contentValidation = """ + Known valid content passes validation without issue. \ + When this test fails then the criteria for the fixture may no longer be valid. + """ + + static let cancellationIsHandled = "Cancellation is sent to a dialog's delegate and logged" static let failureIsHandled = "Failure is sent to a dialog's delegate and logged" + static let successIsHandled = "Success is sent to a dialog's delegate and logged" + static let showingValidDialog = "A dialog shows valid content by creating a bridge API request an opening it" } diff --git a/TestTools/TestTools/SampleError.swift b/TestTools/TestTools/SampleError.swift index 099346815..045a62abe 100644 --- a/TestTools/TestTools/SampleError.swift +++ b/TestTools/TestTools/SampleError.swift @@ -6,6 +6,8 @@ * LICENSE file in the root directory of this source tree. */ -public struct SampleError: Error { +public struct SampleError: Error, Equatable { + private let uuid = UUID() + public init() {} }