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

1128 lines
39 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 Photos
import TestTools
import UIKit
import XCTest
final class ShareDialogTests: XCTestCase {
// swiftlint:disable implicitly_unwrapped_optional
var dialog: ShareDialog!
var delegate: TestSharingDelegate!
var internalURLOpener: TestInternalURLOpener!
var internalUtility: TestInternalUtility!
var settings: TestSettings!
var bridgeAPIRequestFactory: TestBridgeAPIRequestFactory!
var bridgeAPIRequestOpener: TestBridgeAPIRequestOpener!
var socialComposeViewController: TestSocialComposeViewController!
var socialComposeViewControllerFactory: TestSocialComposeViewControllerFactory!
var windowFinder: TestWindowFinder!
var errorFactory: TestErrorFactory!
var eventLogger: TestShareEventLogger!
var mediaLibrarySearcher: TestMediaLibrarySearcher!
// swiftlint:enable implicitly_unwrapped_optional
override func setUp() {
super.setUp()
AccessToken.current = nil
TestShareUtility.reset()
delegate = TestSharingDelegate()
internalURLOpener = TestInternalURLOpener()
internalUtility = TestInternalUtility()
settings = TestSettings()
bridgeAPIRequestFactory = TestBridgeAPIRequestFactory()
bridgeAPIRequestOpener = TestBridgeAPIRequestOpener()
socialComposeViewController = TestSocialComposeViewController()
socialComposeViewControllerFactory = TestSocialComposeViewControllerFactory()
socialComposeViewControllerFactory.stubbedSocialComposeViewController = socialComposeViewController
windowFinder = TestWindowFinder()
errorFactory = TestErrorFactory()
eventLogger = TestShareEventLogger()
mediaLibrarySearcher = TestMediaLibrarySearcher()
ShareDialog.setDependencies(
.init(
internalURLOpener: internalURLOpener,
internalUtility: internalUtility,
settings: settings,
shareUtility: TestShareUtility.self,
bridgeAPIRequestFactory: bridgeAPIRequestFactory,
bridgeAPIRequestOpener: bridgeAPIRequestOpener,
socialComposeViewControllerFactory: socialComposeViewControllerFactory,
windowFinder: windowFinder,
errorFactory: errorFactory,
eventLogger: eventLogger,
mediaLibrarySearcher: mediaLibrarySearcher
)
)
ShareCameraEffectContent.setDependencies(
.init(
internalUtility: internalUtility,
errorFactory: errorFactory
)
)
}
override func tearDown() {
dialog = nil
internalURLOpener = nil
internalUtility = nil
settings = nil
bridgeAPIRequestFactory = nil
bridgeAPIRequestOpener = nil
socialComposeViewController = nil
socialComposeViewControllerFactory = nil
windowFinder = nil
errorFactory = nil
eventLogger = nil
mediaLibrarySearcher = nil
ShareDialog.resetDependencies()
TestShareUtility.reset()
ShareCameraEffectContent.resetDependencies()
AccessToken.current = nil
super.tearDown()
}
// MARK: - Type Dependencies
func testDefaultDependencies() throws {
ShareDialog.resetDependencies()
let dependencies = try ShareDialog.getDependencies()
XCTAssertIdentical(
dependencies.internalURLOpener,
ShareUIApplication.shared,
.DefaultDependencies.usesInternalURLOpenerByDefault
)
XCTAssertIdentical(
dependencies.internalUtility,
InternalUtility.shared,
.DefaultDependencies.usesInternalUtilityByDefault
)
XCTAssertIdentical(dependencies.settings, Settings.shared, .DefaultDependencies.usesSettingsByDefault)
XCTAssertTrue(dependencies.shareUtility is _ShareUtility.Type, .DefaultDependencies.usesShareUtilityByDefault)
XCTAssertTrue(
dependencies.bridgeAPIRequestFactory is ShareBridgeAPIRequestFactory,
.DefaultDependencies.usesShareBridgeAPIRequestFactoryByDefault
)
XCTAssertIdentical(
dependencies.bridgeAPIRequestOpener,
_BridgeAPI.shared,
.DefaultDependencies.usesBridgeAPIByDefault
)
XCTAssertTrue(
dependencies.socialComposeViewControllerFactory is SocialComposeViewControllerFactory,
.DefaultDependencies.usesSocialComposeViewControllerFactoryByDefault
)
XCTAssertIdentical(
dependencies.windowFinder,
InternalUtility.shared,
.DefaultDependencies.usesInternalUtilityAsWindowFinderByDefault
)
XCTAssertTrue(dependencies.errorFactory is _ErrorFactory, .DefaultDependencies.usesErrorFactoryByDefault)
XCTAssertIdentical(
dependencies.eventLogger as AnyObject,
AppEvents.shared,
.DefaultDependencies.usesAppEventsByDefault
)
XCTAssertIdentical(
dependencies.mediaLibrarySearcher as AnyObject,
PHImageManager.default(),
.DefaultDependencies.usesPHImageManagerAsMediaLibrarySearcherByDefault
)
}
func testCustomDependencies() throws {
let dependencies = try ShareDialog.getDependencies()
XCTAssertIdentical(
dependencies.internalURLOpener,
internalURLOpener,
.CustomDependencies.usesCustomInternalURLOpener
)
XCTAssertIdentical(dependencies.internalUtility, internalUtility, .CustomDependencies.usesCustomInternalUtility)
XCTAssertIdentical(dependencies.settings, settings, .CustomDependencies.usesCustomSettings)
XCTAssertTrue(dependencies.shareUtility is TestShareUtility.Type, .CustomDependencies.usesCustomShareUtility)
XCTAssertIdentical(
dependencies.bridgeAPIRequestFactory,
bridgeAPIRequestFactory,
.CustomDependencies.usesCustomShareBridgeAPIRequestFactory
)
XCTAssertIdentical(
dependencies.bridgeAPIRequestOpener,
bridgeAPIRequestOpener,
.CustomDependencies.usesCustomBridgeAPIRequestOpener
)
XCTAssertIdentical(
dependencies.socialComposeViewControllerFactory as AnyObject,
socialComposeViewControllerFactory,
.CustomDependencies.usesCustomSocialComposeViewControllerFactory
)
XCTAssertIdentical(dependencies.windowFinder, windowFinder, .CustomDependencies.usesCustomWindowFinder)
XCTAssertIdentical(dependencies.errorFactory, errorFactory, .CustomDependencies.usesCustomErrorFactory)
XCTAssertIdentical(dependencies.eventLogger as AnyObject, eventLogger, .CustomDependencies.usesCustomEventLogger)
XCTAssertIdentical(
dependencies.mediaLibrarySearcher as AnyObject,
mediaLibrarySearcher,
.CustomDependencies.usesCustomMediaLibrarySearcher
)
}
// MARK: - Construction
func testInitializer() {
let controller = UIViewController()
let content = ShareModelTestUtility.linkContent
let delegate = TestSharingDelegate()
dialog = ShareDialog(viewController: controller, content: content, delegate: delegate)
XCTAssertIdentical(dialog.fromViewController, controller, .Construction.createViaClassFactoryMethod)
XCTAssertIdentical(dialog.shareContent, content, .Construction.createViaClassFactoryMethod)
XCTAssertIdentical(dialog.delegate, delegate, .Construction.createViaClassFactoryMethod)
}
func testClassFactoryMethod() {
let controller = UIViewController()
let content = ShareModelTestUtility.linkContent
let delegate = TestSharingDelegate()
dialog = ShareDialog.dialog(viewController: controller, content: content, delegate: delegate)
XCTAssertIdentical(dialog.fromViewController, controller, .Construction.createViaClassFactoryMethod)
XCTAssertIdentical(dialog.shareContent, content, .Construction.createViaClassFactoryMethod)
XCTAssertIdentical(dialog.delegate, delegate, .Construction.createViaClassFactoryMethod)
}
func testClassShowMethod() {
let controller = UIViewController()
let content = ShareModelTestUtility.linkContent
let delegate = TestSharingDelegate()
let components = WebShareBridgeComponents(methodName: "test", parameters: ["key": "value"])
TestShareUtility.stubbedWebShareBridgeComponents = components
dialog = ShareDialog.show(viewController: controller, content: content, delegate: delegate)
XCTAssertIdentical(dialog.fromViewController, controller, .Construction.createViaClassShowMethod)
XCTAssertIdentical(dialog.shareContent, content, .Construction.createViaClassShowMethod)
XCTAssertIdentical(dialog.delegate, delegate, .Construction.createViaClassShowMethod)
}
// MARK: - Native mode
func testCanShowNativeDialogWithoutShareContent() {
dialog = createEmptyDialog(mode: .native)
internalURLOpener.canOpenURL = true
internalUtility.isFacebookAppInstalled = true
XCTAssertTrue(
dialog.canShow,
"A dialog without share content should be showable on a native dialog"
)
}
func testCanShowNativeLinkContent() {
dialog = createEmptyDialog(mode: .native)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertTrue(
dialog.canShow,
"A dialog with valid link content should be showable on a native dialog"
)
}
func testCanShowNativePhotoContent() {
dialog = createEmptyDialog(mode: .native)
dialog.shareContent = ShareModelTestUtility.photoContent
TestShareUtility.validateShareContentShouldThrow = true
XCTAssertFalse(
dialog.canShow,
"Photo content with photos that have web urls should not be showable on a native dialog"
)
}
func testCanShowNativePhotoContentWithFileURL() {
dialog = createEmptyDialog(mode: .native)
dialog.shareContent = ShareModelTestUtility.photoContentWithFileURLs
XCTAssertTrue(
dialog.canShow,
"Photo content with photos that have file urls should be showable on a native dialog"
)
}
func testCanShowNativeVideoContentWithoutPreviewPhoto() {
dialog = createEmptyDialog(mode: .native)
internalURLOpener.canOpenURL = true
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
XCTAssertTrue(
dialog.canShow,
"Video content without a preview photo should be showable on a native dialog"
)
}
func testCanShowNative() {
dialog = createEmptyDialog(mode: .native)
XCTAssertFalse(
dialog.canShow,
"A native dialog should not be showable if the application is unable to open a url, this can also occur if the api scheme is not whitelisted in the third party app or if the application cannot handle the share API scheme" // swiftlint:disable:this line_length
)
}
func testShowNativeDoesValidate() {
dialog = createEmptyDialog(mode: .native)
dialog.shareContent = ShareModelTestUtility.photoContent
internalURLOpener.canOpenURL = true
XCTAssertFalse(dialog.show())
}
// MARK: - Browser mode
func testCanShowBrowser() {
dialog = createEmptyDialog(mode: .browser)
XCTAssertTrue(
dialog.canShow,
"A dialog without share content should be showable in a browser"
)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertTrue(
dialog.canShow,
"A dialog with link content should be showable in a browser"
)
AccessToken.current = SampleAccessTokens.validToken
dialog.shareContent = ShareModelTestUtility.photoContentWithFileURLs
settings.isAdvertiserTrackingEnabled = true
XCTAssertTrue(
dialog.canShow,
"A dialog with photo content with file urls should be showable in a browser when there is a current access token"
)
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
XCTAssertTrue(
dialog.canShow,
"A dialog with video content without a preview photo should be showable in a browser when there is a current access token" // swiftlint:disable:this line_length
)
}
func testValidateBrowser() throws {
dialog = createEmptyDialog(mode: .browser)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertNoThrow(try dialog.validate())
settings.isAdvertiserTrackingEnabled = true
dialog.shareContent = ShareModelTestUtility.photoContentWithImages
AccessToken.current = SampleAccessTokens.validToken
XCTAssertNoThrow(try dialog.validate())
AccessToken.current = nil
TestShareUtility.stubbedTestShareContainsPhotos = true
AccessToken.current = nil
XCTAssertThrowsError(try dialog.validate())
TestShareUtility.reset()
TestShareUtility.stubbedTestShareContainsVideos = true
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
XCTAssertThrowsError(try dialog.validate())
}
func testSharingViaBrowserWithoutContent() {
bridgeAPIRequestFactory.stubbedBridgeAPIRequest = TestBridgeAPIRequest()
dialog = createEmptyDialog(mode: .browser)
XCTAssertFalse(dialog.show(), .Showing.showingRequiresValidContent)
XCTAssertTrue(delegate.sharerDidFailCalled, .Showing.showingRequiresValidContent)
}
func testSharingViaBrowserWithInvalidLinkContent() {
bridgeAPIRequestFactory.stubbedBridgeAPIRequest = TestBridgeAPIRequest()
dialog = createEmptyDialog(mode: .browser)
let content = ShareModelTestUtility.linkContent
content.contentURL = nil
dialog.shareContent = content
XCTAssertFalse(dialog.show(), .Showing.showingRequiresValidContent)
XCTAssertTrue(delegate.sharerDidFailCalled, .Showing.showingRequiresValidContent)
}
func testSharingViaBrowserWithValidLinkContent() {
let request = TestBridgeAPIRequest()
bridgeAPIRequestFactory.stubbedBridgeAPIRequest = request
let components = WebShareBridgeComponents(methodName: "test", parameters: ["key": "value"])
TestShareUtility.stubbedWebShareBridgeComponents = components
let content = ShareModelTestUtility.linkContent
validate(
shareContent: content,
expectValid: true,
expectShow: true,
mode: .browser
)
XCTAssertIdentical(
TestShareUtility.capturedWebShareBridgeComponentsContent,
content,
.Showing.webShareBridgeComponents
)
XCTAssertEqual(bridgeAPIRequestFactory.capturedProtocolType, .web, .Showing.bridgeAPIRequest)
XCTAssertEqual(bridgeAPIRequestFactory.capturedScheme, URLSchemeEnum.https.rawValue, .Showing.bridgeAPIRequest)
XCTAssertEqual(bridgeAPIRequestFactory.capturedMethodName, components.methodName, .Showing.bridgeAPIRequest)
XCTAssertEqual(
bridgeAPIRequestFactory.capturedParameters as? [String: String],
components.parameters as? [String: String],
.Showing.bridgeAPIRequest
)
XCTAssertNil(bridgeAPIRequestFactory.capturedUserInfo, .Showing.bridgeAPIRequest)
let response = BridgeAPIResponse(request: request, error: nil)
bridgeAPIRequestOpener.capturedCompletionBlock?(response)
XCTAssertTrue(delegate.sharerDidCompleteCalled, .WebDialogDelegate.didCompleteCalled)
XCTAssertIdentical(
internalUtility.unregisterTransientObjectObject as AnyObject,
dialog,
.WebDialogDelegate.unregistersTransientObject
)
}
func testSharingViaBrowserWithValidPhotoContent() {
settings.isAdvertiserTrackingEnabled = true
let request = TestBridgeAPIRequest()
bridgeAPIRequestFactory.stubbedBridgeAPIRequest = request
let components = WebShareBridgeComponents(methodName: "test", parameters: ["key": "value"])
TestShareUtility.stubbedWebShareBridgeComponents = components
let content = ShareModelTestUtility.photoContentWithImages
validate(
shareContent: content,
expectValid: true,
expectShow: true,
mode: .browser
)
XCTAssertIdentical(
TestShareUtility.capturedAsyncWebPhotoContentContent,
content,
.Showing.webPhotoContent
)
let parameters = ["key": "value"]
TestShareUtility.capturedAsyncWebPhotoContentCompletion?(true, "test", parameters)
XCTAssertEqual(bridgeAPIRequestFactory.capturedProtocolType, .web, .Showing.bridgeAPIRequest)
XCTAssertEqual(bridgeAPIRequestFactory.capturedScheme, URLSchemeEnum.https.rawValue, .Showing.bridgeAPIRequest)
XCTAssertEqual(bridgeAPIRequestFactory.capturedMethodName, "test", .Showing.bridgeAPIRequest)
XCTAssertEqual(
bridgeAPIRequestFactory.capturedParameters as? [String: String],
parameters,
.Showing.bridgeAPIRequest
)
XCTAssertNil(bridgeAPIRequestFactory.capturedUserInfo, .Showing.bridgeAPIRequest)
let response = BridgeAPIResponse(request: request, error: nil)
bridgeAPIRequestOpener.capturedCompletionBlock?(response)
XCTAssertTrue(delegate.sharerDidCompleteCalled, .WebDialogDelegate.didCompleteCalled)
XCTAssertIdentical(
internalUtility.unregisterTransientObjectObject as AnyObject,
dialog,
.WebDialogDelegate.unregistersTransientObject
)
}
// MARK: - Web mode
func testCanShowWebWithTrackingPermission() {
settings.isAdvertiserTrackingEnabled = true
dialog = createEmptyDialog(mode: .web)
XCTAssertTrue(
dialog.canShow,
"A dialog without share content should be showable on web"
)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertTrue(
dialog.canShow,
"A dialog with link content should be showable on web"
)
AccessToken.current = SampleAccessTokens.validToken
TestShareUtility.stubbedTestShareContainsPhotos = true
TestShareUtility.validateShareContentShouldThrow = true
dialog.shareContent = ShareModelTestUtility.photoContent
XCTAssertFalse(
dialog.canShow,
"A dialog with photos should not be showable on web"
)
TestShareUtility.reset()
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
TestShareUtility.stubbedTestShareContainsMedia = true
XCTAssertFalse(
dialog.canShow,
"A dialog with content that contains local media should not be showable on web"
)
}
func testCanShowWebWithoutTrackingPermissions() {
dialog = createEmptyDialog(mode: .web)
XCTAssertFalse(
dialog.canShow,
"A dialog without share content should be showable on web"
)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertFalse(
dialog.canShow,
"A dialog with link content should not be showable on web"
)
}
func testValidateWebWithTrackingPermission() throws {
settings.isAdvertiserTrackingEnabled = true
dialog = createEmptyDialog(mode: .web)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertNoThrow(try dialog.validate())
AccessToken.current = SampleAccessTokens.validToken
dialog.shareContent = ShareModelTestUtility.photoContent
TestShareUtility.validateShareContentShouldThrow = true
XCTAssertThrowsError(
try dialog.validate(),
"A dialog with photo content that points to remote urls should not be considered valid on web"
)
TestShareUtility.reset()
dialog.shareContent = ShareModelTestUtility.photoContentWithImages
TestShareUtility.stubbedTestShareContainsPhotos = true
XCTAssertThrowsError(
try dialog.validate(),
"A dialog with photo content that is already loaded should not be considered valid on web"
)
TestShareUtility.reset()
dialog.shareContent = ShareModelTestUtility.photoContentWithFileURLs
TestShareUtility.stubbedTestShareContainsPhotos = true
XCTAssertThrowsError(
try dialog.validate(),
"A dialog with photo content that points to file urls should not be considered valid on web"
)
TestShareUtility.reset()
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
TestShareUtility.stubbedTestShareContainsMedia = true
XCTAssertThrowsError(
try dialog.validate(),
"A dialog that includes local media should not be considered valid on web"
)
TestShareUtility.reset()
AccessToken.current = nil
TestShareUtility.stubbedTestShareContainsVideos = true
XCTAssertThrowsError(
try dialog.validate(),
"A dialog with content but no access token should not be considered valid on web"
)
}
// MARK: - Feed browser mode
func testCanShowFeedBrowser() {
dialog = createEmptyDialog(mode: .feedBrowser)
XCTAssertTrue(
dialog.canShow,
"A dialog without content should be showable in a browser feed"
)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertTrue(
dialog.canShow,
"A dialog with link content should be showable in a browser feed"
)
dialog.shareContent = ShareModelTestUtility.photoContent
XCTAssertFalse(
dialog.canShow,
"A dialog with photo content should not be showable in a browser feed"
)
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
XCTAssertFalse(
dialog.canShow,
"A dialog with video content that has no preview photo should not be showable in a browser feed"
)
}
func testValidateFeedBrowser() throws {
dialog = createEmptyDialog(mode: .feedBrowser)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertNoThrow(try dialog.validate())
dialog.shareContent = ShareModelTestUtility.photoContentWithImages
XCTAssertThrowsError(try dialog.validate())
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
XCTAssertThrowsError(try dialog.validate())
}
// MARK: - Share sheet mode
func testValidateShareSheet() throws {
dialog = createEmptyDialog(mode: .shareSheet)
dialog.shareContent = ShareModelTestUtility.linkContentWithoutQuote
XCTAssertNoThrow(
try dialog.validate(),
"Should not throw an error when validating link content without quotes"
)
dialog.shareContent = ShareModelTestUtility.photoContentWithImages
XCTAssertNoThrow(
try dialog.validate(),
"Should not throw an error when validating photo content with images"
)
dialog.shareContent = ShareModelTestUtility.photoContent
XCTAssertThrowsError(
try dialog.validate(),
"Should throw an error when validating photo content on a share sheet"
)
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
XCTAssertThrowsError(
try dialog.validate(),
"Should throw an error when validating video content without a preview photo on a share sheet"
)
}
func testThatInitialTextIsSetCorrectlyWhenShareExtensionIsAvailable() throws {
dialog = createEmptyDialog(mode: .shareSheet)
let content = ShareModelTestUtility.linkContent
content.hashtag = Hashtag("#hashtag")
TestShareUtility.stubbedHashtagString = "#hashtag"
content.quote = "a quote"
dialog.shareContent = content
internalUtility.isFacebookAppInstalled = true
internalURLOpener.canOpenURL = true
settings.appID = "appID"
let viewController = UIViewController()
dialog.fromViewController = viewController
XCTAssertTrue(dialog.show())
try validateInitialText(
capturedText: socialComposeViewController.capturedInitialText,
expectedAppID: "appID",
expectedHashtag: "#hashtag",
expectedQuotes: ["a quote"]
)
}
func testPassingValidationForLinkQuoteWithValidShareExtensionVersion() {
internalUtility.isFacebookAppInstalled = true
validate(
shareContent: ShareModelTestUtility.linkContent,
expectValid: true,
expectShow: true,
mode: .shareSheet
)
}
func testValidateWithErrorReturnsFalseForMMPIfAValidShareExtensionVersionIsNotAvailable() {
TestShareUtility.validateShareContentShouldThrow = true
validate(
shareContent: ShareModelTestUtility.mediaContent,
expectValid: false,
expectShow: false,
mode: .shareSheet,
nonSupportedScheme: "fbapi20160328:/"
)
}
func testThatValidateWithErrorReturnsTrueForMMPIfAValidShareExtensionVersionIsAvailable() {
internalUtility.isFacebookAppInstalled = true
validate(
shareContent: ShareModelTestUtility.mediaContent,
expectValid: true,
expectShow: true,
mode: .shareSheet
)
}
func testThatValidateWithErrorReturnsFalseForMMPWithMoreThan1Video() {
validate(
shareContent: ShareModelTestUtility.multiVideoMediaContent,
expectValid: false,
expectShow: false,
mode: .shareSheet
)
}
// MARK: - Feed web mode
func testCanShowFeedWebWithTrackingPermission() {
settings.isAdvertiserTrackingEnabled = true
dialog = createEmptyDialog(mode: .feedWeb)
XCTAssertTrue(
dialog.canShow,
"A dialog without content should be showable in a web feed"
)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertTrue(
dialog.canShow,
"A dialog with link content should be showable in a web feed"
)
dialog.shareContent = ShareModelTestUtility.photoContent
XCTAssertFalse(
dialog.canShow,
"A dialog with photo content should not be showable in a web feed"
)
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
XCTAssertFalse(
dialog.canShow,
"A dialog with video content and no preview photo should not be showable in a web feed"
)
}
func testCanShowFeedWebWithoutTrackingPermission() {
settings.isAdvertiserTrackingEnabled = false
dialog = createEmptyDialog(mode: .feedWeb)
XCTAssertFalse(
dialog.canShow,
"A dialog without tracking permissions should not be showable in a web feed"
)
}
func testValidateFeedWebWithTrackingPermission() throws {
settings.isAdvertiserTrackingEnabled = true
dialog = createEmptyDialog(mode: .feedWeb)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertNoThrow(try dialog.validate())
dialog.shareContent = ShareModelTestUtility.photoContentWithImages
XCTAssertThrowsError(try dialog.validate())
dialog.shareContent = ShareModelTestUtility.videoContentWithoutPreviewPhoto
XCTAssertThrowsError(try dialog.validate())
}
func testValidateFeedWebWithoutTrackingPermission() throws {
settings.isAdvertiserTrackingEnabled = false
dialog = createEmptyDialog(mode: .feedWeb)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertThrowsError(
try dialog.validate(),
"A web view dialog without tracking permission should not pass validation"
)
}
// MARK: - Automatic mode
func testCameraShareModesWhenNativeUnavailable() {
dialog = createEmptyDialog(mode: .automatic)
dialog.shareContent = ShareModelTestUtility.cameraEffectContent
XCTAssertThrowsError(try dialog.validate())
}
// MARK: - Multiple modes
func testCameraShareModesWhenNativeAvailable() throws {
dialog = createEmptyDialog(mode: .automatic)
dialog.shareContent = ShareModelTestUtility.cameraEffectContent
internalURLOpener.canOpenURL = true
internalUtility.isFacebookAppInstalled = true
// Check supported modes
dialog.mode = .automatic
XCTAssertNoThrow(try dialog.validate())
dialog.mode = .native
XCTAssertNoThrow(try dialog.validate())
// Check unsupported modes
dialog.mode = .web
XCTAssertThrowsError(try dialog.validate())
dialog.mode = .browser
XCTAssertThrowsError(try dialog.validate())
dialog.mode = .shareSheet
XCTAssertThrowsError(try dialog.validate())
dialog.mode = .feedWeb
XCTAssertThrowsError(try dialog.validate())
dialog.mode = .feedBrowser
XCTAssertThrowsError(try dialog.validate())
}
// MARK: - WebDialogDelegate
func testWebDialogDelegateCancellation() {
dialog = createEmptyDialog(mode: .web)
let webDialog = _WebDialog(name: "test")
webDialog.delegate = dialog
dialog.webDialog = webDialog
dialog.webDialogDidCancel(webDialog)
XCTAssertNil(dialog.webDialog, .WebDialogDelegate.clearsWebDialog)
XCTAssertTrue(delegate.sharerDidCancelCalled, .WebDialogDelegate.didCancelCalled)
XCTAssertIdentical(delegate.sharerDidCancelSharer, dialog, .WebDialogDelegate.didCancelCalled)
XCTAssertIdentical(
internalUtility.unregisterTransientObjectObject as AnyObject,
dialog,
.WebDialogDelegate.unregistersTransientObject
)
}
func testWebDialogDelegateFailure() throws {
dialog = createEmptyDialog(mode: .web)
let webDialog = _WebDialog(name: "test")
webDialog.delegate = dialog
dialog.webDialog = webDialog
let error = TestSDKError(type: .unknown)
dialog.webDialog(webDialog, didFailWithError: error)
XCTAssertNil(dialog.webDialog, .WebDialogDelegate.clearsWebDialog)
XCTAssertTrue(delegate.sharerDidFailCalled, .WebDialogDelegate.didFailCalled)
XCTAssertIdentical(delegate.sharerDidFailSharer, dialog, .WebDialogDelegate.didFailCalled)
XCTAssertIdentical(delegate.sharerDidFailError as AnyObject, error, .WebDialogDelegate.didFailCalled)
XCTAssertIdentical(
internalUtility.unregisterTransientObjectObject as AnyObject,
dialog,
.WebDialogDelegate.unregistersTransientObject
)
}
func testWebDialogDelegateCompletionWithCancelErrorCode() {
dialog = createEmptyDialog(mode: .web)
let webDialog = _WebDialog(name: "test")
webDialog.delegate = dialog
dialog.webDialog = webDialog
dialog.webDialog(webDialog, didCompleteWithResults: ["error_code": 4201])
XCTAssertNil(dialog.webDialog, .WebDialogDelegate.clearsWebDialog)
XCTAssertTrue(delegate.sharerDidCancelCalled, .WebDialogDelegate.didCancelCalled)
XCTAssertIdentical(delegate.sharerDidCancelSharer, dialog, .WebDialogDelegate.didCancelCalled)
XCTAssertIdentical(
internalUtility.unregisterTransientObjectObject as AnyObject,
dialog,
.WebDialogDelegate.unregistersTransientObject
)
}
func testWebDialogDelegateCompletionWithError() throws {
dialog = createEmptyDialog(mode: .web)
let webDialog = _WebDialog(name: "test")
webDialog.delegate = dialog
dialog.webDialog = webDialog
dialog.webDialog(
webDialog,
didCompleteWithResults: [
"error_code": 123,
"error_message": "message",
]
)
XCTAssertNil(dialog.webDialog, .WebDialogDelegate.clearsWebDialog)
XCTAssertTrue(delegate.sharerDidFailCalled, .WebDialogDelegate.didFailCalled)
XCTAssertIdentical(delegate.sharerDidFailSharer, dialog, .WebDialogDelegate.didFailCalled)
let error = try XCTUnwrap(delegate.sharerDidFailError as? TestSDKError, .WebDialogDelegate.didFailCalled)
XCTAssertEqual(error.domain, ShareErrorDomain, .WebDialogDelegate.didFailCalled)
XCTAssertEqual(error.code, ShareError.unknown.rawValue, .WebDialogDelegate.didFailCalled)
XCTAssertEqual(error.userInfo[GraphRequestErrorGraphErrorCodeKey] as? Int, 123, .WebDialogDelegate.didFailCalled)
XCTAssertEqual(error.message, "message", .WebDialogDelegate.didFailCalled)
XCTAssertNil(error.underlyingError, .WebDialogDelegate.didFailCalled)
XCTAssertIdentical(
internalUtility.unregisterTransientObjectObject as AnyObject,
dialog,
.WebDialogDelegate.unregistersTransientObject
)
}
func testWebDialogDelegateCompletionWithCompletionGestureCancellation() {
dialog = createEmptyDialog(mode: .web)
let webDialog = _WebDialog(name: "test")
webDialog.delegate = dialog
dialog.webDialog = webDialog
dialog.webDialog(
webDialog,
didCompleteWithResults: [
ShareBridgeAPI.CompletionGesture.key: ShareBridgeAPI.CompletionGesture.cancelValue,
"error_code": 0,
]
)
XCTAssertNil(dialog.webDialog, .WebDialogDelegate.clearsWebDialog)
XCTAssertTrue(delegate.sharerDidCancelCalled, .WebDialogDelegate.didCancelCalled)
XCTAssertIdentical(delegate.sharerDidCancelSharer, dialog, .WebDialogDelegate.didCancelCalled)
XCTAssertIdentical(
internalUtility.unregisterTransientObjectObject as AnyObject,
dialog,
.WebDialogDelegate.unregistersTransientObject
)
}
func testWebDialogDelegateCompletion() throws {
dialog = createEmptyDialog(mode: .web)
let webDialog = _WebDialog(name: "test")
webDialog.delegate = dialog
dialog.webDialog = webDialog
dialog.webDialog(
webDialog,
didCompleteWithResults: [
ShareBridgeAPI.PostIDKey.webParameters: "my-post",
"error_code": 0,
]
)
XCTAssertNil(dialog.webDialog, .WebDialogDelegate.clearsWebDialog)
XCTAssertTrue(delegate.sharerDidCompleteCalled, .WebDialogDelegate.didCompleteCalled)
XCTAssertIdentical(delegate.sharerDidCompleteSharer, dialog, .WebDialogDelegate.didCompleteCalled)
let results = try XCTUnwrap(delegate.sharerDidCompleteResults, .WebDialogDelegate.didCompleteCalled)
XCTAssertEqual(
results[ShareBridgeAPI.PostIDKey.results] as? String,
"my-post",
.WebDialogDelegate.didCompleteCalled
)
XCTAssertIdentical(
internalUtility.unregisterTransientObjectObject as AnyObject,
dialog,
.WebDialogDelegate.unregistersTransientObject
)
}
// MARK: - Helpers
func createEmptyDialog(mode: ShareDialog.Mode) -> ShareDialog {
let dialog = ShareDialog(viewController: nil, content: nil, delegate: delegate)
dialog.mode = mode
return dialog
}
func validate(
shareContent: SharingContent,
expectValid: Bool,
expectShow: Bool,
mode: ShareDialog.Mode,
nonSupportedScheme: String? = nil,
file: StaticString = #file,
line: UInt = #line
) {
internalURLOpener.computeCanOpenURL = { url in
url.absoluteString != nonSupportedScheme
}
let viewController = UIViewController()
dialog = createEmptyDialog(mode: mode)
dialog.shareContent = shareContent
dialog.fromViewController = viewController
if expectValid {
XCTAssertNoThrow(
try dialog.validate(),
"Should not throw an error when validating the dialog",
file: file,
line: line
)
} else {
XCTAssertThrowsError(
try dialog.validate(),
"Should not throw an error when validating the dialog"
)
}
XCTAssertEqual(
dialog.show(),
expectShow,
"Showing the dialog should \(expectShow ? "succeed" : "fail")",
file: file,
line: line
)
}
private func validateInitialText(
capturedText: String?,
expectedAppID: String,
expectedHashtag: String,
expectedQuotes: [String],
file: StaticString = #file,
line: UInt = #line
) throws {
let capturedComponents = try XCTUnwrap(initialTextComponents(for: capturedText))
XCTAssertEqual(
capturedComponents.appID,
"fb-app-id:\(expectedAppID)",
file: file,
line: line
)
XCTAssertEqual(
capturedComponents.hashtag,
expectedHashtag,
file: file,
line: line
)
XCTAssertEqual(
capturedComponents.jsonObject.count,
3,
file: file,
line: line
)
XCTAssertEqual(
capturedComponents.jsonObject["quotes"] as? [String],
expectedQuotes,
file: file,
line: line
)
XCTAssertEqual(
capturedComponents.jsonObject["app_id"] as? String,
expectedAppID,
file: file,
line: line
)
XCTAssertEqual(
capturedComponents.jsonObject["hashtags"] as? [String],
[expectedHashtag],
file: file,
line: line
)
}
private typealias InitialTextComponents = (appID: String, hashtag: String, jsonObject: [String: Any])
private func initialTextComponents(for potentialText: String?) -> InitialTextComponents? {
guard
let splitAtVerticalBar = potentialText?.split(separator: "|"),
splitAtVerticalBar.count == 2,
let beforeJSON = splitAtVerticalBar.first?.split(separator: " "),
beforeJSON.count == 2,
let appID = beforeJSON.first,
let hashtag = beforeJSON.last,
let json = splitAtVerticalBar.last,
let data = String(json).data(using: .utf8),
let jsonObject = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any]
else {
return nil
}
return (
appID: String(appID),
hashtag: String(hashtag),
jsonObject: jsonObject
)
}
}
// swiftformat:disable extensionaccesscontrol
// MARK: - Assumptions
fileprivate extension String {
enum DefaultDependencies {
static let usesInternalURLOpenerByDefault = """
The default internal URL opening dependency should be the shared UIApplication
"""
static let usesInternalUtilityByDefault = """
The default internal utility dependency should be the shared InternalUtility
"""
static let usesSettingsByDefault = "The default settings dependency should be the shared Settings"
static let usesShareUtilityByDefault = "The default share utility dependency should be the _ShareUtility class"
static let usesShareBridgeAPIRequestFactoryByDefault = """
The default bridge API request factory dependency should be a concrete ShareBridgeAPIRequestFactory
"""
static let usesBridgeAPIByDefault = """
The default bridge API request opening dependency should be the shared BridgeAPI for its default
"""
static let usesSocialComposeViewControllerFactoryByDefault = """
The default social compose view controller factory dependency should be a concrete \
SocialComposeViewControllerFactory
"""
static let usesInternalUtilityAsWindowFinderByDefault = """
The default window finding dependency should be the shared InternalUtility
"""
static let usesErrorFactoryByDefault = "The default error factory dependency should be a concrete _ErrorFactory"
static let usesAppEventsByDefault = "The default event logging dependency should be the shared AppEvents"
static let usesPHImageManagerAsMediaLibrarySearcherByDefault = """
The default media library searching dependency should be the default PHImageManager
"""
}
enum CustomDependencies {
static let usesCustomInternalURLOpener = "The internal URL opening dependency should be configurable"
static let usesCustomInternalUtility = "The internal utility dependency should be configurable"
static let usesCustomSettings = "The settings dependency should be configurable"
static let usesCustomShareUtility = "The share utility dependency should be configurable"
static let usesCustomShareBridgeAPIRequestFactory = """
The bridge API request factory dependency should be configurable
"""
static let usesCustomBridgeAPIRequestOpener = "The bridge API request opening dependency should be configurable"
static let usesCustomSocialComposeViewControllerFactory = """
The social compose view roller factory dependency should be configurable
"""
static let usesCustomWindowFinder = "The window finding dependency should be configurable"
static let usesCustomErrorFactory = "The error factory dependency should be configurable"
static let usesCustomEventLogger = "The event logging dependency should be configurable"
static let usesCustomMediaLibrarySearcher = "The media library searching dependency should be configurable"
}
enum Construction {
static let createViaClassFactoryMethod = "Can create a dialog with a class factory method"
static let createViaClassShowMethod = "Can create and show a dialog with the class `show` method"
}
enum WebDialogDelegate {
static let didCancelCalled = "A dialog invokes its delegate's cancellation method"
static let didFailCalled = "A dialog invokes its delegate's failure method"
static let didCompleteCalled = "A dialog invokes its delegate's completion method"
static let clearsWebDialog = "A dialog clears its web dialog"
static let unregistersTransientObject = "A dialog unregisters itself as a transient object"
}
enum Showing {
static let showingRequiresValidContent = "A share dialog will only show with valid share content"
static let showsWithValidContent = "A share dialog shows with valid content"
static let bridgeAPIRequest = "A bridge API request is created with the appropriate values"
static let webShareBridgeComponents = "A bridge API request needs components derived from its content"
static let webPhotoContent = "A bridge API request needs values generated for web photo content"
}
}