DailyView/Carthage/Checkouts/facebook-ios-sdk/FBSDKShareKit/FBSDKShareKitTests/UserInterface/MessageDialogTests.swift
2025-12-30 16:40:31 +08:00

441 lines
18 KiB
Swift

/*
* 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.
*/
@testable import FBSDKShareKit
import FBSDKCoreKit
import TestTools
import XCTest
final class MessageDialogTests: XCTestCase {
// swiftlint:disable implicitly_unwrapped_optional
var dialog: MessageDialog!
var shareDialogConfiguration: TestShareDialogConfiguration!
var delegate: TestSharingDelegate!
// Type dependencies
var accessTokenWallet: TestAccessTokenWallet.Type!
var bridgeAPIRequestFactory: TestBridgeAPIRequestFactory!
var bridgeAPIRequestOpener: TestBridgeAPIRequestOpener!
var errorFactory: TestErrorFactory!
var eventLogger: TestShareEventLogger!
var internalUtility: TestInternalUtility!
var shareUtility: TestShareUtility.Type!
// swiftlint:enable implicitly_unwrapped_optional
override func setUp() {
super.setUp()
accessTokenWallet = TestAccessTokenWallet.self
accessTokenWallet.current = SampleAccessTokens.validToken
bridgeAPIRequestFactory = TestBridgeAPIRequestFactory()
bridgeAPIRequestOpener = TestBridgeAPIRequestOpener()
errorFactory = TestErrorFactory()
eventLogger = TestShareEventLogger()
internalUtility = TestInternalUtility()
shareUtility = TestShareUtility.self
MessageDialog.setDependencies(
.init(
accessTokenWallet: accessTokenWallet,
bridgeAPIRequestFactory: bridgeAPIRequestFactory,
bridgeAPIRequestOpener: bridgeAPIRequestOpener,
errorFactory: errorFactory,
eventLogger: eventLogger,
internalUtility: internalUtility,
shareUtility: shareUtility
)
)
shareDialogConfiguration = TestShareDialogConfiguration()
delegate = TestSharingDelegate()
dialog = MessageDialog(
content: nil,
delegate: delegate,
shareDialogConfiguration: shareDialogConfiguration
)
}
override func tearDown() {
accessTokenWallet = nil
TestAccessTokenWallet.reset()
bridgeAPIRequestFactory = nil
bridgeAPIRequestOpener = nil
errorFactory = nil
eventLogger = nil
internalUtility = nil
shareUtility = nil
TestShareUtility.reset()
MessageDialog.resetDependencies()
shareDialogConfiguration = nil
delegate = nil
dialog = nil
super.tearDown()
}
func testDefaultTypeDependencies() throws {
MessageDialog.resetDependencies()
let dependencies = try MessageDialog.getDependencies()
XCTAssertTrue(
dependencies.accessTokenWallet is AccessToken.Type,
.defaultDependency("AccessToken", for: "access token wallet")
)
XCTAssertTrue(
dependencies.bridgeAPIRequestFactory is ShareBridgeAPIRequestFactory,
.defaultDependency("a bridge API request factory", for: "bridge API request factory")
)
XCTAssertIdentical(
dependencies.bridgeAPIRequestOpener,
_BridgeAPI.shared,
.defaultDependency("the shared BridgeAPI", for: "bridge API request opening")
)
XCTAssertTrue(
dependencies.errorFactory is _ErrorFactory,
.defaultDependency("a concrete error factory", for: "error factory")
)
XCTAssertIdentical(
dependencies.eventLogger as AnyObject,
AppEvents.shared,
.defaultDependency("the shared AppEvents", for: "event logging")
)
XCTAssertIdentical(
dependencies.internalUtility,
InternalUtility.shared,
.defaultDependency("the shared InternalUtility", for: "internal utility")
)
XCTAssertTrue(
dependencies.shareUtility is _ShareUtility.Type,
.defaultDependency("_ShareUtility", for: "share utility")
)
}
func testCustomTypeDependencies() throws {
let dependencies = try MessageDialog.getDependencies()
XCTAssertIdentical(
dependencies.accessTokenWallet,
accessTokenWallet,
.customDependency(for: "access token wallet")
)
XCTAssertIdentical(
dependencies.bridgeAPIRequestFactory,
bridgeAPIRequestFactory,
.customDependency(for: "bridge API request factory")
)
XCTAssertIdentical(
dependencies.bridgeAPIRequestOpener,
bridgeAPIRequestOpener,
.customDependency(for: "bridge API request opening")
)
XCTAssertIdentical(
dependencies.errorFactory,
errorFactory,
.customDependency(for: "error factory")
)
XCTAssertIdentical(
dependencies.eventLogger as AnyObject,
eventLogger,
.customDependency(for: "event logging")
)
XCTAssertIdentical(
dependencies.internalUtility,
internalUtility,
.customDependency(for: "internal utility")
)
XCTAssertTrue(
dependencies.shareUtility is TestShareUtility.Type,
.customDependency(for: "share utility")
)
}
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
XCTAssertTrue(dialog.canShow)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertTrue(dialog.canShow)
dialog.shareContent = ShareModelTestUtility.photoContent
XCTAssertTrue(dialog.canShow)
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
XCTAssertTrue(dialog.canShow)
internalUtility.isMessengerAppInstalled = false
XCTAssertFalse(dialog.canShow)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertFalse(dialog.canShow)
dialog.shareContent = ShareModelTestUtility.photoContent
XCTAssertFalse(dialog.canShow)
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
XCTAssertFalse(dialog.canShow)
}
func testValidate() {
dialog = MessageDialog()
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertNoThrow(try dialog.validate(), .contentValidation)
dialog.shareContent = ShareModelTestUtility.photoContentWithImages
XCTAssertNoThrow(try dialog.validate(), .contentValidation)
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
XCTAssertNoThrow(try dialog.validate(), .contentValidation)
dialog.shareContent = ShareModelTestUtility.cameraEffectContent
XCTAssertNil(
try? dialog.validate(),
"Should not successfully validate share content that is known to be missing content"
)
}
func testShowInvokesDelegateWhenCannotShow() throws {
shareDialogConfiguration.stubbedShouldUseNativeDialog = true
internalUtility.isMessengerAppInstalled = false
dialog.show()
let error = try XCTUnwrap(delegate.sharerDidFailError as? TestSDKError, .failureIsHandled)
XCTAssertEqual(error.type, .general, .failureIsHandled)
XCTAssertEqual(error.domain, ShareErrorDomain, .failureIsHandled)
XCTAssertEqual(error.code, ShareError.dialogNotAvailable.rawValue, .failureIsHandled)
XCTAssertTrue(error.userInfo.isEmpty, .failureIsHandled)
XCTAssertEqual(error.message, "Message dialog is not available.", .failureIsHandled)
XCTAssertNil(error.underlyingError, .failureIsHandled)
}
func testShowInvokesDelegateWhenMissingContent() throws {
shareDialogConfiguration.stubbedShouldUseNativeDialog = true
internalUtility.isMessengerAppInstalled = true
dialog.show()
let error = try XCTUnwrap(delegate.sharerDidFailError as? TestSDKError, .failureIsHandled)
XCTAssertEqual(error.type, .requiredArgument, .failureIsHandled)
XCTAssertEqual(error.domain, ShareErrorDomain, .failureIsHandled)
XCTAssertEqual(error.code, TestSDKError.testErrorCode, .failureIsHandled)
XCTAssertTrue(error.userInfo.isEmpty, .failureIsHandled)
XCTAssertNil(error.message, .failureIsHandled)
XCTAssertEqual(error.name, "shareContent", .failureIsHandled)
XCTAssertNil(error.underlyingError, .failureIsHandled)
}
func testShowInvokesDelegateWhenCannotValidate() throws {
dialog = MessageDialog(
content: ShareModelTestUtility.cameraEffectContent,
delegate: delegate,
shareDialogConfiguration: shareDialogConfiguration
)
shareDialogConfiguration.stubbedShouldUseNativeDialog = true
internalUtility.isMessengerAppInstalled = true
dialog.show()
let error = try XCTUnwrap(delegate.sharerDidFailError as? TestSDKError, .failureIsHandled)
XCTAssertEqual(error.type, .requiredArgument, .failureIsHandled)
XCTAssertEqual(error.domain, ShareErrorDomain, .failureIsHandled)
XCTAssertEqual(error.code, TestSDKError.testErrorCode, .failureIsHandled)
XCTAssertTrue(error.userInfo.isEmpty, .failureIsHandled)
XCTAssertEqual(error.message, "Message dialog does not support ShareCameraEffectContent.", .failureIsHandled)
XCTAssertNil(error.underlyingError, .failureIsHandled)
}
func testFailingToShowWithoutBridgeAPIRequest() {
internalUtility.isMessengerAppInstalled = true
shareDialogConfiguration.stubbedShouldUseNativeDialog = true
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertFalse(dialog.show(), .showingFailsWithoutBridgeAPIRequest)
}
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, URLSchemeEnum.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)
}
}
// swiftformat:disable extensionaccesscontrol
// MARK: - Assumptions
fileprivate extension String {
static func defaultDependency(_ dependency: String, for type: String) -> String {
"The MessageDialog type uses \(dependency) as its \(type) dependency by default"
}
static func customDependency(for type: String) -> 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 showingFailsWithoutBridgeAPIRequest = "A dialog does not show if a bridge API request cannot be created"
static let showingValidDialog = "A dialog shows valid content by creating a bridge API request an opening it"
}