/*
 * 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 Foundation

enum TestShareUtility {
  static func reset() {
    resetAsyncWebPhotoContentValues()
    resetWebShareBridgeComponentsValues()
    resetBridgeParametersValues()

    resetContentFlagValues()
    resetHashtagValues()

    resetRequiredValueValues()
    resetValidateArgumentValues()
    resetValidateArrayValues()
    resetValidateNetworkURLValues()
    resetValidateShareContentValues()
  }
}

extension TestShareUtility: ShareUtilityProtocol {

  static func feedShareDictionary(for content: SharingContent) -> [String: Any]? {
    [:]
  }

  // MARK: Async web photo content

  static var capturedAsyncWebPhotoContentContent: SharePhotoContent?
  static var capturedAsyncWebPhotoContentCompletion: WebPhotoContentHandler?

  static func buildAsyncWebPhotoContent(
    _ content: SharePhotoContent,
    completion: @escaping WebPhotoContentHandler
  ) {
    capturedAsyncWebPhotoContentContent = content
    capturedAsyncWebPhotoContentCompletion = completion
  }

  static func resetAsyncWebPhotoContentValues() {
    capturedAsyncWebPhotoContentContent = nil
    capturedAsyncWebPhotoContentCompletion = nil
  }

  // MARK: Web share bridge components

  static var stubbedWebShareBridgeComponents: WebShareBridgeComponents?
  static var capturedWebShareBridgeComponentsContent: SharingContent?

  static func buildWebShareBridgeComponents(for content: SharingContent) -> WebShareBridgeComponents {
    guard let components = stubbedWebShareBridgeComponents else {
      fatalError("Missing stubbed components")
    }

    capturedWebShareBridgeComponentsContent = content
    return components
  }

  static func resetWebShareBridgeComponentsValues() {
    stubbedWebShareBridgeComponents = nil
    capturedWebShareBridgeComponentsContent = nil
  }

  // MARK: Bridge parameters

  static var capturedBridgeParametersShareContent: SharingContent?
  static var capturedBridgeParametersBridgeOptions: ShareBridgeOptions?
  static var capturedBridgeParametersShouldFailOnDataError: Bool? // swiftlint:disable:this discouraged_optional_boolean
  static var stubbedBridgeParameters: [String: Any]?

  static func bridgeParameters(
    for shareContent: SharingContent,
    options bridgeOptions: ShareBridgeOptions,
    shouldFailOnDataError: Bool
  ) -> [String: Any] {
    capturedBridgeParametersShareContent = shareContent
    capturedBridgeParametersBridgeOptions = bridgeOptions
    capturedBridgeParametersShouldFailOnDataError = shouldFailOnDataError

    return stubbedBridgeParameters ?? [:]
  }

  static func resetBridgeParametersValues() {
    capturedBridgeParametersShareContent = nil
    capturedBridgeParametersBridgeOptions = nil
    capturedBridgeParametersShouldFailOnDataError = nil
    stubbedBridgeParameters = nil
  }

  // MARK: Share media content

  static func shareMediaContentContainsPhotosAndVideos(_ shareMediaContent: ShareMediaContent) -> Bool {
    false
  }

  // MARK: Content flags

  static var stubbedTestShareContainsMedia = false
  static var stubbedTestShareContainsPhotos = false
  static var stubbedTestShareContainsVideos = false

  static func getContentFlags(for shareContent: SharingContent) -> ContentFlags {
    ContentFlags(
      containsMedia: stubbedTestShareContainsMedia,
      containsPhotos: stubbedTestShareContainsPhotos,
      containsVideos: stubbedTestShareContainsVideos
    )
  }

  static func resetContentFlagValues() {
    stubbedTestShareContainsMedia = false
    stubbedTestShareContainsPhotos = false
    stubbedTestShareContainsVideos = false
  }

  // MARK: Hashtags

  static var stubbedHashtagString: String?

  static func hashtagString(from hashtag: Hashtag?) -> String? {
    stubbedHashtagString ?? ""
  }

  static func resetHashtagValues() {
    stubbedHashtagString = nil
  }
}

extension TestShareUtility: ShareValidating {

  struct ValidationError: Error {}

  // MARK: Validate required value

  static var validateRequiredValueShouldThrow = false
  static var validateRequiredValueValue: Any?
  static var validateRequiredValueName: String?

  static func validateRequiredValue(_ value: Any, named name: String) throws {
    validateRequiredValueValue = value
    validateRequiredValueName = name

    if validateRequiredValueShouldThrow {
      throw ValidationError()
    }
  }

  static func resetRequiredValueValues() {
    validateRequiredValueShouldThrow = false
    validateRequiredValueValue = nil
    validateRequiredValueName = nil
  }

  // MARK: Validate argument

  static var validateArgumentShouldThrow = false

  static func validateArgument<Argument>(
    _ value: Argument,
    named name: String,
    in possibleValues: Set<Argument>
  ) throws where Argument: Hashable {
    if validateArgumentShouldThrow {
      throw ValidationError()
    }
  }

  static func resetValidateArgumentValues() {
    validateArgumentShouldThrow = false
  }

  // MARK: Validate array

  static var validateArrayShouldThrow = false
  static var validateArrayArray: [Any]?
  static var validateArrayMinCount: Int?
  static var validateArrayMaxCount: Int?
  static var validateArrayName: String?

  static func validateArray(_ array: [Any], minCount: Int, maxCount: Int, named name: String) throws {
    validateArrayArray = array
    validateArrayMinCount = minCount
    validateArrayMaxCount = maxCount
    validateArrayName = name

    if validateArrayShouldThrow {
      throw ValidationError()
    }
  }

  static func resetValidateArrayValues() {
    validateArrayShouldThrow = false
    validateArrayArray = nil
    validateArrayMinCount = nil
    validateArrayMaxCount = nil
    validateArrayName = nil
  }

  // MARK: Validate network URL

  static var validateNetworkURLShouldThrow = false

  static func validateNetworkURL(_ url: URL, named name: String) throws {
    if validateNetworkURLShouldThrow {
      throw ValidationError()
    }
  }

  static func resetValidateNetworkURLValues() {
    validateNetworkURLShouldThrow = false
  }

  // MARK: Validate share content

  static var validateShareContentShouldThrow = false

  static func validateShareContent(
    _ shareContent: SharingContent,
    options bridgeOptions: ShareBridgeOptions = []
  ) throws {
    if validateShareContentShouldThrow {
      throw ValidationError()
    }
  }

  static func resetValidateShareContentValues() {
    validateShareContentShouldThrow = false
  }
}


/*
 * 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 Foundation

final class TestUserInterfaceStringProvider: UserInterfaceStringProviding {
  var bundleForStrings: Bundle { Bundle(for: Self.self) }
}


/*
 * 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 FBSendButtonTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  var button: FBSendButton!
  var content: ShareLinkContent!
  var testDialog: TestMessageDialog!
  var internalUtility: TestInternalUtility!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    button = FBSendButton()
    content = ShareLinkContent()
    testDialog = TestMessageDialog()
    internalUtility = TestInternalUtility()

    FBSendButton.setDependencies(.init(internalUtility: internalUtility))
  }

  override func tearDown() {
    button = nil
    content = nil
    testDialog = nil
    internalUtility = nil

    FBSendButton.resetDependencies()

    super.tearDown()
  }

  func testDefaultDependencies() throws {
    FBSendButton.resetDependencies()

    let dependencies = try FBSendButton.getDependencies()
    XCTAssertIdentical(dependencies.internalUtility, InternalUtility.shared, .usesInternalUtilityByDefault)
  }

  func testCustomDependencies() throws {
    let dependencies = try FBSendButton.getDependencies()
    XCTAssertIdentical(dependencies.internalUtility, internalUtility, .usesCustomInternalUtility)
  }

  func testMissingDialogSetBySuperInitializer() {
    XCTAssertNotNil(button.dialog, .hasNoDialogByDefault)
  }

  func testHasNoContentByDefault() {
    XCTAssertNil(button.shareContent, .hasNoContentByDefault)
  }

  func testContentIsFromDialog() {
    button.dialog?.shareContent = content
    XCTAssertIdentical(button.shareContent, content, .contentComesFromDialog)
  }

  func testContentIsSetOnDialog() {
    button.isEnabled = true
    button.shareContent = content
    XCTAssertIdentical(button.dialog?.shareContent, content, .contentIsSetOnDialog)
    XCTAssertFalse(button.isEnabled, .settingContentUpdatesEnabledState)
  }

  func testHasNoAnalyticsParameters() {
    XCTAssertNil(button.analyticsParameters, .hasNoAnalyticsParameters)
  }

  func testHasImpressionTrackingValues() {
    XCTAssertEqual(button.impressionTrackingEventName, .sendButtonImpression, .hasImpressionTrackingEventName)
    XCTAssertEqual(button.impressionTrackingIdentifier, "send", .hasImpressionTrackingIdentifier)
  }

  func testImplicitlyDisabledWithoutDialog() {
    button.dialog = nil
    XCTAssertTrue(button.isImplicitlyDisabled, .isImplicitlyDisabledWithoutDialog)
  }

  func testImplicitlyDisabledWithoutShowableDialog() {
    button.dialog = testDialog
    testDialog.stubbedCanShow = false
    XCTAssertTrue(button.isImplicitlyDisabled, .isImplicitlyDisabledWithoutShowableDialog)
  }

  func testImplicitlyDisabledWithoutValidatedDialog() {
    button.dialog = testDialog
    testDialog.stubbedCanShow = true
    testDialog.stubbedValidationSucceeds = false
    XCTAssertTrue(button.isImplicitlyDisabled, .isImplicitlyDisabledWithoutValidatedDialog)
  }

  func testImplicitlyEnabledWithValidatedDialog() {
    button.dialog = testDialog
    testDialog.stubbedCanShow = true
    testDialog.stubbedValidationSucceeds = true
    XCTAssertFalse(button.isImplicitlyDisabled, .isImplicitlyEnabledWithValidatedDialog)
  }

  func testConfiguringSetsAppearance() throws {
    XCTAssertEqual(button.title(for: .normal), "Send", .configuringSetsAppearance)
  }

  func testConfiguringSetsAction() throws {
    let actions = try XCTUnwrap(button.actions(forTarget: button, forControlEvent: .touchUpInside))
    XCTAssertEqual(actions.count, 1, .configuringSetsAction)
    let action = try XCTUnwrap(actions.first, .configuringSetsAction)
    XCTAssertEqual(action, "share", .configuringSetsAction)
  }

  func testConfiguringAddsDialog() {
    button.dialog = nil
    button.configureButton()
    XCTAssertNotNil(button.dialog, .configuringAddsDialog)
  }

  func testAction() {
    button.dialog = testDialog
    button.share()
    XCTAssertTrue(testDialog.wasShowCalled, .actionShowsDialog)
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let usesInternalUtilityByDefault = "The default internal utility dependency should be the InternalUtility type"
  static let usesCustomInternalUtility = "The internal utility dependency should be configurable"

  static let hasNoDialogByDefault = "A send button does not have a message dialog by default"

  static let hasNoContentByDefault = "A send button does not have share content by default"
  static let contentComesFromDialog = "The share content should be derived from its dialog, if any"
  static let contentIsSetOnDialog = "Setting the share content should set it on its dialog, if any"
  static let settingContentUpdatesEnabledState = "Setting the share content should update the enabled state"

  static let hasNoAnalyticsParameters = "A send button has no analytics parameters"

  static let hasImpressionTrackingEventName = "A send button has a custom impression tracking event name"
  static let hasImpressionTrackingIdentifier = "A send button has a custom impression tracking identifier"

  static let isImplicitlyDisabledWithoutDialog = "A send button is implicitly disabled without a dialog"
  static let isImplicitlyDisabledWithoutShowableDialog = """
    A send button is implicitly disabled without a showable dialog
    """
  static let isImplicitlyDisabledWithoutValidatedDialog = """
    A send button is implicitly disabled without a validated dialog
    """
  static let isImplicitlyEnabledWithValidatedDialog = "A send button is implicitly enabled with a validated dialog"

  static let configuringSetsAppearance = "Configuring a button should set up its appearance"
  static let configuringSetsAction = "Configuring a button should set an action against itself calling the share method"
  static let configuringAddsDialog = "Configuring a button should create and set a message dialog"

  static let actionShowsDialog = "A button's action should show its dialog, if any"
}

// MARK: - Test Values

fileprivate extension URL {
  static let linkContent = URL(string: "https://facebook.com")! // swiftlint:disable:this force_unwrapping
}


/*
 * 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 FBShareButtonTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  var button: FBShareButton!
  var content: ShareLinkContent!
  var testDialog: TestShareDialog!
  var stringProvider: TestUserInterfaceStringProvider!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    stringProvider = TestUserInterfaceStringProvider()
    FBShareButton.setDependencies(.init(stringProvider: stringProvider))

    content = ShareLinkContent()
    testDialog = TestShareDialog()
    button = FBShareButton()
  }

  override func tearDown() {
    button = nil
    content = nil
    testDialog = nil
    stringProvider = nil

    FBShareButton.resetDependencies()

    super.tearDown()
  }

  func testDefaultDependencies() throws {
    FBShareButton.resetDependencies()

    let dependencies = try FBShareButton.getDependencies()
    XCTAssertIdentical(dependencies.stringProvider as AnyObject, InternalUtility.shared, .usesInternalUtilityByDefault)
  }

  func testCustomDependencies() throws {
    let dependencies = try FBShareButton.getDependencies()
    XCTAssertIdentical(dependencies.stringProvider as AnyObject, stringProvider, .usesCustomStringProvider)
  }

  func testMissingDialogSetBySuperInitializer() {
    XCTAssertNotNil(button.dialog, .hasNoDialogByDefault)
  }

  func testHasNoContentByDefault() {
    XCTAssertNil(button.shareContent, .hasNoContentByDefault)
  }

  func testContentIsFromDialog() {
    button.dialog?.shareContent = content
    XCTAssertIdentical(button.shareContent, content, .contentComesFromDialog)
  }

  func testContentIsSetOnDialog() {
    button.isEnabled = true
    button.shareContent = content
    XCTAssertIdentical(button.dialog?.shareContent, content, .contentIsSetOnDialog)
    XCTAssertFalse(button.isEnabled, .settingContentUpdatesEnabledState)
  }

  func testHasNoAnalyticsParameters() {
    XCTAssertNil(button.analyticsParameters, .hasNoAnalyticsParameters)
  }

  func testHasImpressionTrackingValues() {
    XCTAssertEqual(button.impressionTrackingEventName, .shareButtonImpression, .hasImpressionTrackingEventName)
    XCTAssertEqual(button.impressionTrackingIdentifier, "share", .hasImpressionTrackingIdentifier)
  }

  func testImplicitlyDisabledWithoutDialog() {
    button.dialog = nil
    XCTAssertTrue(button.isImplicitlyDisabled, .isImplicitlyDisabledWithoutDialog)
  }

  func testImplicitlyDisabledWithoutShowableDialog() {
    button.dialog = testDialog
    testDialog.stubbedCanShow = false
    XCTAssertTrue(button.isImplicitlyDisabled, .isImplicitlyDisabledWithoutShowableDialog)
  }

  func testImplicitlyDisabledWithoutValidatedDialog() {
    button.dialog = testDialog
    testDialog.stubbedCanShow = true
    testDialog.stubbedValidationSucceeds = false
    XCTAssertTrue(button.isImplicitlyDisabled, .isImplicitlyDisabledWithoutValidatedDialog)
  }

  func testImplicitlyEnabledWithValidatedDialog() {
    button.dialog = testDialog
    testDialog.stubbedCanShow = true
    testDialog.stubbedValidationSucceeds = true
    XCTAssertFalse(button.isImplicitlyDisabled, .isImplicitlyEnabledWithValidatedDialog)
  }

  func testConfiguringSetsAppearance() throws {
    XCTAssertEqual(button.title(for: .normal), "Share", .configuringSetsAppearance)
  }

  func testConfiguringSetsAction() throws {
    let actions = try XCTUnwrap(button.actions(forTarget: button, forControlEvent: .touchUpInside))
    XCTAssertEqual(actions.count, 1, .configuringSetsAction)
    let action = try XCTUnwrap(actions.first, .configuringSetsAction)
    XCTAssertEqual(action, "share", .configuringSetsAction)
  }

  func testConfiguringAddsDialog() {
    button.dialog = nil
    button.configureButton()
    XCTAssertNotNil(button.dialog, .configuringAddsDialog)
  }

  func testAction() {
    button.dialog = testDialog
    button.share()
    XCTAssertTrue(testDialog.wasShowCalled, .actionShowsDialog)
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let usesInternalUtilityByDefault = """
    The default string providing dependency should be the shared InternalUtility
    """
  static let usesCustomStringProvider = "The string providing dependency should be configurable"

  static let hasNoDialogByDefault = "A share button does not have a share dialog by default"

  static let hasNoContentByDefault = "A share button does not have share content by default"
  static let contentComesFromDialog = "The share content should be derived from its dialog, if any"
  static let contentIsSetOnDialog = "Setting the share content should set it on its dialog, if any"
  static let settingContentUpdatesEnabledState = "Setting the share content should update the enabled state"

  static let hasNoAnalyticsParameters = "A share button has no analytics parameters"

  static let hasImpressionTrackingEventName = "A share button has a custom impression tracking event name"
  static let hasImpressionTrackingIdentifier = "A share button has a custom impression tracking identifier"

  static let isImplicitlyDisabledWithoutDialog = "A share button is implicitly disabled without a dialog"
  static let isImplicitlyDisabledWithoutShowableDialog = """
    A share button is implicitly disabled without a showable dialog
    """
  static let isImplicitlyDisabledWithoutValidatedDialog = """
    A share button is implicitly disabled without a validated dialog
    """
  static let isImplicitlyEnabledWithValidatedDialog = "A share button is implicitly enabled with a validated dialog"

  static let configuringSetsAppearance = "Configuring a button should set up its appearance"
  static let configuringSetsAction = "Configuring a button should set an action against itself calling the share method"
  static let configuringAddsDialog = "Configuring a button should create and set a share dialog"

  static let actionShowsDialog = "A button's action should show its dialog, if any"
}


/*
 * 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, 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)
  }
}

// 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"
}


/*
 * 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()
    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
    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())

    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, URLScheme.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() {
    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, URLScheme.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 testCanShowWeb() {
    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 testValidateWeb() throws {
    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 testCanShowFeedWeb() {
    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 testValidateFeedWeb() throws {
    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())
  }

  // 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"
  }
}


/*
 * 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 XCTest

final class SocialComposeViewControllerFactoryTests: XCTestCase {

  var factory: SocialComposeViewControllerFactory! // swiftlint:disable:this implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()
    factory = SocialComposeViewControllerFactory()
  }

  func testMakingViewController() {
    let controller = factory.makeSocialComposeViewController()
    XCTAssertEqual(controller.serviceType, "com.apple.social.facebook", .controllerHasFacebookServiceType)
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let controllerHasFacebookServiceType = "A controller with the Facebook service type should be made"
}


/*
 * 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 TestTools

final class TestMessageDialog: MessageDialog {
  // swiftlint:disable:next swiftlint_disable_without_this_or_next
  // swiftlint:disable discouraged_optional_boolean
  var stubbedCanShow: Bool?
  var stubbedValidationSucceeds: Bool?
  var wasShowCalled = false
  // swiftlint:enable discouraged_optional_boolean

  convenience init() {
    self.init(
      content: nil,
      delegate: nil,
      shareDialogConfiguration: TestShareDialogConfiguration()
    )
  }

  override var canShow: Bool {
    stubbedCanShow ?? super.canShow
  }

  override func validate() throws {
    struct ValidationError: Error {}

    guard let validationSucceeds = stubbedValidationSucceeds else {
      return try super.validate()
    }

    if validationSucceeds {
      return
    } else {
      throw ValidationError()
    }
  }

  override func show() -> Bool {
    wasShowCalled = true
    return super.show()
  }
}


/*
 * 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 TestTools

final class TestShareDialog: ShareDialog {
  // swiftlint:disable:next swiftlint_disable_without_this_or_next
  // swiftlint:disable discouraged_optional_boolean
  var stubbedCanShow: Bool?
  var stubbedValidationSucceeds: Bool?
  var wasShowCalled = false
  // swiftlint:enable discouraged_optional_boolean

  convenience init() {
    self.init(
      viewController: nil,
      content: nil,
      delegate: nil
    )
  }

  override var canShow: Bool {
    stubbedCanShow ?? super.canShow
  }

  override func validate() throws {
    struct ValidationError: Error {}

    guard let validationSucceeds = stubbedValidationSucceeds else {
      return try super.validate()
    }

    if validationSucceeds {
      return
    } else {
      throw ValidationError()
    }
  }

  override func show() -> Bool {
    wasShowCalled = true
    return super.show()
  }
}


/*
 * 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

final class TestShareDialogConfiguration: ShareDialogConfigurationProtocol {

  var stubbedShouldUseNativeDialog = false

  func shouldUseNativeDialog(forDialogName dialogName: String) -> Bool {
    stubbedShouldUseNativeDialog
  }

  var capturedShouldUseSafariViewControllerDialogName: String?
  var stubbedShouldUseSafariViewController = false

  func shouldUseSafariViewController(forDialogName dialogName: String) -> Bool {
    capturedShouldUseSafariViewControllerDialogName = dialogName
    return stubbedShouldUseSafariViewController
  }
}


/*
 * 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 FBSDKShareKit

final class TestSharingDelegate: SharingDelegate {
  var sharerDidCompleteCalled = false
  var sharerDidCompleteSharer: Sharing?
  var sharerDidCompleteResults: [String: Any]?

  func sharer(_ sharer: Sharing, didCompleteWithResults results: [String: Any]) {
    sharerDidCompleteCalled = true
    sharerDidCompleteSharer = sharer
    sharerDidCompleteResults = results
  }

  var sharerDidFailCalled = false
  var sharerDidFailSharer: Sharing?
  var sharerDidFailError: Error?

  func sharer(_ sharer: Sharing, didFailWithError error: Error) {
    sharerDidFailCalled = true
    sharerDidFailSharer = sharer
    sharerDidFailError = error
  }

  var sharerDidCancelCalled = false
  var sharerDidCancelSharer: Sharing?

  func sharerDidCancel(_ sharer: Sharing) {
    sharerDidCancelCalled = true
    sharerDidCancelSharer = sharer
  }
}


/*
 * 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 Foundation
import Social
import UIKit

final class TestSocialComposeViewControllerFactory: SocialComposeViewControllerFactoryProtocol {
  var stubbedSocialComposeViewController: SLComposeViewController?

  func makeSocialComposeViewController() -> SLComposeViewController {
    guard let viewController = stubbedSocialComposeViewController else {
      fatalError("A stubbed view controller is required")
    }

    return viewController
  }
}

final class TestSocialComposeViewController: SLComposeViewController {
  var stubbedSetInitialText = false
  var capturedInitialText: String?

  override func setInitialText(_ text: String) -> Bool {
    capturedInitialText = text
    return stubbedSetInitialText
  }

  override func add(_ image: UIImage) -> Bool {
    false
  }

  override func add(_ url: URL) -> Bool {
    false
  }
}


/*
 * 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 FacebookLogin
import UIKit

@UIApplicationMain
final class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        ApplicationDelegate.shared.application(
            application,
            didFinishLaunchingWithOptions: launchOptions
        )

        return true
    }

    func application(
        _ app: UIApplication,
        open url: URL,
        options: [UIApplication.OpenURLOptionsKey : Any] = [:]
    ) -> Bool {
        return ApplicationDelegate.shared.application(
            app,
            open: url,
            options: options
        )
    }

    // MARK: UISceneSession Lifecycle

    @available(iOS 13.0, *)
    func application(
        _ application: UIApplication,
        configurationForConnecting connectingSceneSession: UISceneSession,
        options: UIScene.ConnectionOptions
    ) -> UISceneConfiguration {
        return UISceneConfiguration(
            name: "Default Configuration",
            sessionRole: connectingSceneSession.role
        )
    }

}


/*
 * 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 FacebookLogin
import UIKit

final class LoginButtonViewController: LoginViewController {

    @IBOutlet private weak var loginButton: FBLoginButton!
    @IBOutlet private weak var useLimitedLoginSwitch: UISwitch!
    @IBOutlet private weak var nonceTextField: UITextField!

    var loginTracking: LoginTracking {
        useLimitedLoginSwitch.isOn ? .limited : .enabled
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        loginButton.loginTracking = loginTracking
        loginButton.delegate = self
    }

}

extension LoginButtonViewController: LoginButtonDelegate {

    func loginButtonWillLogin(_ loginButton: FBLoginButton) -> Bool {
        loginButton.loginTracking = loginTracking

        if let nonce = nonceTextField?.text, !nonce.isEmpty {
            loginButton.nonce = nonce
        }

        return true
    }

    func loginButton(
        _ loginButton: FBLoginButton,
        didCompleteWith potentialResult: LoginManagerLoginResult?,
        error potentialError: Error?
    ) {
        if let error = potentialError {
            return presentAlert(for: error)
        }

        guard let result = potentialResult else {
            return presentAlert(
                title: "Invalid Result",
                message: "Login attempt failed"
            )
        }

        guard !result.isCancelled else {
            return presentAlert(
                title: "Cancelled",
                message: "Login attempt was cancelled"
            )
        }

        showLoginDetails()
    }

    func loginButtonDidLogOut(_ loginButton: FBLoginButton) {
        presentAlert(title: "Logged Out", message: "You are now logged out.")
    }

}


/*
 * 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 FacebookCore
import FacebookLogin
import UIKit

final class LoginDetailsViewController: UIViewController {

    @IBOutlet private weak var accessTokenLabel: UILabel!
    @IBOutlet private weak var permissionsLabel: UILabel!
    @IBOutlet private weak var declinedPermissionsLabel: UILabel!
    @IBOutlet private weak var authenticationTokenLabel: UILabel!
    @IBOutlet private weak var nonceLabel: UILabel!
    @IBOutlet private weak var nameLabel: UILabel!
    @IBOutlet private weak var userIdentifierLabel: UILabel!
    @IBOutlet private weak var emailLabel: UILabel!

    private static let missingProfile = "Current Profile unavailable"
    private static let missingAccessToken = "Access Token Unavailable"

    override func viewDidLoad() {
        super.viewDidLoad()

        let accessToken = AccessToken.current
        let authenticationToken = AuthenticationToken.current
        let profile = Profile.current

        accessTokenLabel.text = accessToken?.tokenString
            ?? Self.missingAccessToken
        permissionsLabel.text = accessToken?.permissions
            .map { $0.name }
            .joined(separator: ", ")
            ?? Self.missingAccessToken
        declinedPermissionsLabel.text = accessToken?.declinedPermissions
            .map { $0.name }
            .joined(separator: ", ")
            ?? Self.missingAccessToken
        authenticationTokenLabel.text = authenticationToken?.tokenString
            ?? "Authentication Token Unavailable"
        nonceLabel.text = authenticationToken?.nonce
            ?? "Authentication Token Nonce Unavailable"
        nameLabel.text = profile?.name ?? Self.missingProfile
        userIdentifierLabel.text = profile?.userID ?? Self.missingProfile
        emailLabel.text = profile?.email ?? "Email unavailable"
    }

}


/*
 * 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 FacebookLogin
import UIKit

final class LoginManagerViewController: LoginViewController {

    @IBOutlet private weak var loginButton: UIButton!
    @IBOutlet private weak var useLimitedLoginSwitch: UISwitch!
    @IBOutlet private weak var nonceTextField: UITextField!

    private var trackingPreference: LoginTracking {
        useLimitedLoginSwitch.isOn ? .limited : .enabled
    }

    private var nonce: String? {
        nonceTextField.text
    }

    private let loginManager = LoginManager()

    var configuration: LoginConfiguration? {
        if let nonce = nonce, !nonce.isEmpty {
            return LoginConfiguration(
                permissions: [.publicProfile, .email],
                tracking: trackingPreference,
                nonce: nonce
            )
        }
        else {
            return LoginConfiguration(
                permissions: [.publicProfile, .email],
                tracking: trackingPreference
            )
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        updateLoginButton()
    }

    @IBAction func toggleLoginState() {
        guard !isLoggedIn else {
            loginManager.logOut()
            return updateLoginButton()
        }

        invokeLoginMethod()
    }

    @IBAction func invokeLoginMethod() {
        guard let validConfiguration = configuration else {
            return presentAlert(
                title: "Invalid Configuration",
                message: "Please provide a valid login configuration"
            )
        }

        loginManager.logIn(
            viewController: self,
            configuration: validConfiguration
        ) { [unowned self] result in
            switch result {
            case .cancelled:
                self.presentAlert(
                    title: "Cancelled",
                    message: "Login attempt was cancelled"
                )
            case .failed(let error):
                self.presentAlert(for: error)
            case .success:
                self.updateLoginButton()
                self.showLoginDetails()
            }
        }
    }

    func updateLoginButton() {
        loginButton.setTitle(
            isLoggedIn ? "Log Out" : "Log In With Facebook",
            for: .normal
        )
    }

}


/*
 * 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 UIKit
import FacebookCore

/// A base class to avoid polluting the UIViewController namespace
class LoginViewController: UIViewController {

    var isLoggedIn: Bool {
        return AccessToken.current != nil || AuthenticationToken.current != nil
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        guard let appID = Bundle.main.object(forInfoDictionaryKey: "FacebookAppID") as? String,
            appID != "{your-app-id}"
            else {
                return presentAlert(
                    title: "Invalid App Identifier",
                    message: "Please enter your Facebook application identifier in your Info.plist. This can be found on the developer portal at developers.facebook.com"
                )
        }

        guard let urlTypes = Bundle.main.object(forInfoDictionaryKey: "CFBundleURLTypes") as? [[String: [String]]],
            let scheme = urlTypes.first?["CFBundleURLSchemes"]?.first,
            scheme != "fb{your-app-id}"
            else {
                return presentAlert(
                    title: "Invalid URL Scheme",
                    message: "Please update the url scheme in your Info.plist with your Facebook application identifier to allow for the login flow to reopen this app"
                )
        }
    }

    func presentAlert(title: String, message: String) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
        let dismissAction = UIAlertAction(title: "Dismiss", style: .default, handler: nil)
        alertController.addAction(dismissAction)

        DispatchQueue.main.async {
            self.present(alertController, animated: true)
        }
    }

    func presentAlert(for error: Error) {
        let nsError = error as NSError

        guard let sdkMessage = nsError.userInfo["com.facebook.sdk:FBSDKErrorDeveloperMessageKey"] as? String
            else {
                preconditionFailure("Errors from the SDK should have a developer facing message")
        }

        presentAlert(title: "Login Error", message: sdkMessage)
    }

    func showLoginDetails() {
        performSegue(withIdentifier: "showLoginDetails", sender: self)
    }

}


/*
 * 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 FacebookCore
import UIKit

@available(iOS 13.0, *)
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let context = URLContexts.first else {
            fatalError("Open url called without a context. This should never happen.")
        }

        ApplicationDelegate.shared.application(
            UIApplication.shared,
            open: context.url,
            sourceApplication: nil,
            annotation: nil
        )
    }

}


/*
 * 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 UIKit
import FacebookCore

@UIApplicationMain
final class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        ApplicationDelegate.shared.application(
            application,
            didFinishLaunchingWithOptions: launchOptions
        )
        return true
    }

    func application(
        _ application: UIApplication,
        open url: URL,
        sourceApplication: String?,
        annotation: Any
    ) -> Bool {
        return ApplicationDelegate.shared.application(
            application, open: url,
            sourceApplication: sourceApplication,
            annotation: annotation
        )
    }

    func application(
        _ app: UIApplication,
        open url: URL,
        options: [UIApplication.OpenURLOptionsKey: Any] = [:]
    ) -> Bool {
        return ApplicationDelegate.shared.application(
            app,
            open: url,
            options: options
        )
    }
}


/*
 * 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 FacebookCore
import UIKit

final class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let context = URLContexts.first else {
            fatalError("Open url called without a context. This should never happen.")
        }

        ApplicationDelegate.shared.application(
            UIApplication.shared,
            open: context.url,
            sourceApplication: nil,
            annotation: nil
        )
    }
}


/*
 * 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 FacebookShare
import UIKit

final class ShareViewController: UITableViewController {

    @IBAction func shareLink() {
        guard let url = URL(string: "https://newsroom.fb.com/") else {
            preconditionFailure("URL is invalid")
        }

        let content = ShareLinkContent()
        content.contentURL = url
        content.hashtag = Hashtag("#bestSharingSampleEver")

        dialog(withContent: content).show()
    }

    private var isSimulator: Bool {
#if targetEnvironment(simulator)
        return true
#else
        return false
#endif
    }

    @IBAction func sharePhoto() {
        guard !isSimulator else {
            presentAlert(
                title: "Error",
                message: "Sharing an image will not work on a simulator. Please build to a device and try again."
            )
            return
        }

        guard let image = UIImage(named: "puppy") else {
            presentAlert(
                title: "Invalid image",
                message: "Could not find image to share"
            )
            return
        }

        let photo = SharePhoto(image: image, userGenerated: true)
        let content = SharePhotoContent()
        content.photos = [photo]

        let dialog = self.dialog(withContent: content)

        // Recommended to validate before trying to display the dialog
        do {
            try dialog.validate()
        } catch {
            presentAlert(for: error)
        }

        dialog.show()
    }

    func dialog(withContent content: SharingContent) -> ShareDialog {
        return ShareDialog(
            viewController: self,
            content: content,
            delegate: self
        )
    }
}

extension ShareViewController: SharingDelegate {

    func sharer(_ sharer: Sharing, didCompleteWithResults results: [String : Any]) {
        print(results)
    }

    func sharer(_ sharer: Sharing, didFailWithError error: Error) {
        presentAlert(for: error)
    }

    func sharerDidCancel(_ sharer: Sharing) {
        presentAlert(title: "Cancelled", message: "Sharing cancelled")
    }
}