FBSDKShareKit/FBSDKShareKitTests/Content/ShareVideoContentTests.swift (300 lines of code) (raw):
/*
* 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 ShareVideoContentTests: XCTestCase {
// swiftlint:disable implicitly_unwrapped_optional
var content: ShareVideoContent!
var validator: TestShareUtility.Type!
var mediaLibrarySearcher: TestMediaLibrarySearcher!
var errorFactory: TestErrorFactory!
var testError: TestSDKError!
// swiftlint:enable implicitly_unwrapped_optional
let originalParameters = ["original": "value"]
override func setUp() {
super.setUp()
validator = TestShareUtility.self
validator.reset()
mediaLibrarySearcher = TestMediaLibrarySearcher()
ShareVideoContent.setDependencies(
.init(
validator: TestShareUtility.self,
mediaLibrarySearcher: mediaLibrarySearcher
)
)
errorFactory = TestErrorFactory()
testError = TestSDKError(type: .unknown)
errorFactory.stubbedError = testError
ShareVideo.setDependencies(.init(errorFactory: errorFactory))
}
override func tearDown() {
ShareVideo.resetDependencies()
ShareVideoContent.resetDependencies()
validator.reset()
validator = nil
mediaLibrarySearcher = nil
errorFactory = nil
testError = nil
content = nil
super.tearDown()
}
func testDefaultDependencies() throws {
ShareVideoContent.resetDependencies()
let dependencies = try ShareVideoContent.getDependencies()
XCTAssertTrue(
dependencies.validator is _ShareUtility.Type,
.usesShareUtilityAsShareValidatorByDefault
)
XCTAssertIdentical(
dependencies.mediaLibrarySearcher as AnyObject,
PHImageManager.default(),
.usesPHImageManagerAsMediaLibrarySearcherByDefault
)
}
func testCustomDependencies() throws {
let dependencies = try ShareVideoContent.getDependencies()
XCTAssertTrue(dependencies.validator is TestShareUtility.Type, .usesCustomShareValidator)
XCTAssertIdentical(
dependencies.mediaLibrarySearcher as AnyObject,
mediaLibrarySearcher,
.usesCustomMediaLibrarySearcher
)
}
func testProperties() {
content = .allProperties
XCTAssertIdentical(content.video as AnyObject, ShareVideo.plain, .hasVideo)
XCTAssertEqual(content.contentURL, .content, .hasContentURL)
XCTAssertIdentical(content.hashtag as AnyObject, Hashtag.sample, .hasHashtag)
XCTAssertEqual(content.peopleIDs, .peopleIDs, .hasPeopleIDs)
XCTAssertEqual(content.placeID, .placeID, .hasPlaceID)
XCTAssertEqual(content.ref, .ref, .hasRef)
XCTAssertEqual(content.pageID, .pageID, .hasPageID)
XCTAssertNotNil(content.shareUUID, .hasShareUUID)
}
// MARK: - Validation
func testValidationWithInvalidContent() throws {
content = ShareVideoContent()
XCTAssertThrowsError(try content.validate(options: []), .validationValidatesVideo) { _ in
XCTAssertIdentical(validator.validateRequiredValueValue as? ShareVideo, content.video, .validationValidatesVideo)
XCTAssertEqual(validator.validateRequiredValueName, "video", .validationValidatesVideo)
}
}
func testValidationWithValidContent() throws {
content = .valid(.withAssetURLSource)
XCTAssertNoThrow(try content.validate(options: []), .validationValidatesVideo)
XCTAssertIdentical(validator.validateRequiredValueValue as? ShareVideo, content.video, .validationValidatesVideo)
XCTAssertEqual(validator.validateRequiredValueName, "video", .validationValidatesVideo)
}
// MARK: - Bridge Parameters
func testInvalidVideoBridgeParameters() throws {
content = ShareVideoContent()
let parameters = content.addParameters(originalParameters, options: [])
try validateOriginalParameters(in: parameters)
try validateEmptyVideoParameters(in: parameters, .invalidVideoParameters)
}
// MARK: Asset Source
func testAssetSourceBridgeParametersWithAssetOption() throws {
content = .valid(.withAssetSource)
let parameters = content.addParameters(originalParameters, options: .videoAsset)
try validateOriginalParameters(in: parameters)
let videoParameters = try getVideoParameters(from: parameters, message: .assetIdentifierParameter)
let identifier = try XCTUnwrap(videoParameters["assetIdentifier"] as? String, .assetIdentifierParameter)
XCTAssertEqual(identifier, PHAsset.testAsset.localIdentifier, .assetIdentifierParameter)
}
func testAssetSourceBridgeParametersWithoutAssetOption() throws {
content = .valid(.withAssetSource)
mediaLibrarySearcher.stubbedGetVideoURL = .asset
let parameters = content.addParameters(originalParameters, options: [])
try validateOriginalParameters(in: parameters)
XCTAssertIdentical(mediaLibrarySearcher.getVideoURLAsset, ShareVideo.withAssetSource.videoAsset, .assetURLParameter)
let videoParameters = try getVideoParameters(from: parameters, message: .assetURLParameter)
let url = try XCTUnwrap(videoParameters["assetURL"] as? URL, .assetURLParameter)
XCTAssertEqual(url, .asset, .assetURLParameter)
}
// MARK: Data Source
func testDataSourceBridgeParametersWithoutDataOption() throws {
content = .valid(.withDataSource)
let parameters = content.addParameters(originalParameters, options: [])
try validateOriginalParameters(in: parameters)
try validateEmptyVideoParameters(in: parameters, .dataVideoParametersWithoutDataOption)
}
func testDataSourceBridgeParametersWithDataOption() throws {
content = .valid(.withDataSource)
let parameters = content.addParameters(originalParameters, options: [.videoData])
try validateOriginalParameters(in: parameters)
let videoParameters = try getVideoParameters(from: parameters, message: .dataVideoParametersWithDataOption)
let data = try XCTUnwrap(videoParameters["data"] as? Data, .dataVideoParametersWithDataOption)
XCTAssertEqual(data, .video, .dataVideoParametersWithDataOption)
}
// MARK: URL Source
func testRemoteURLSourceBridgeParameters() throws {
content = .valid(.withRemoteURLSource)
let parameters = content.addParameters(originalParameters, options: [])
try validateOriginalParameters(in: parameters)
try validateEmptyVideoParameters(in: parameters, .remoteURLVideoParameters)
}
func testURLSourceBridgeParametersWithAssetURL() throws {
content = .valid(.withAssetURLSource)
let parameters = content.addParameters(originalParameters, options: [])
try validateOriginalParameters(in: parameters)
let videoParameters = try getVideoParameters(from: parameters, message: .urlVideoWithAssetURLParameters)
let url = try XCTUnwrap(videoParameters["assetURL"] as? URL, .urlVideoWithAssetURLParameters)
XCTAssertEqual(url, .asset, .urlVideoWithAssetURLParameters)
}
func testURLSourceBridgeParametersWithFileURLWithoutDataOption() throws {
content = .valid(.withFileURLSource)
let parameters = content.addParameters(originalParameters, options: [])
try validateOriginalParameters(in: parameters)
try validateEmptyVideoParameters(in: parameters, .fileURLVideoParametersWithoutDataOption)
}
func testVideoURLBridgeParametersWithFileURLWithDataOption() throws {
content = .valid(.withFileURLSource)
let parameters = content.addParameters(originalParameters, options: [.videoData])
try validateOriginalParameters(in: parameters)
let videoParameters = try getVideoParameters(from: parameters, message: .fileURLVideoParametersWithDataOption)
let data = try XCTUnwrap(videoParameters["data"] as? Data, .fileURLVideoParametersWithDataOption)
XCTAssertEqual(data, .video, .fileURLVideoParametersWithDataOption)
}
func testMissingPreviewPhotoBridgeParameters() throws {
content = .withoutPreviewPhoto
let parameters = content.addParameters(originalParameters, options: [])
let videoParameters = try getVideoParameters(from: parameters, message: .missingPreviewPhotoParameter)
XCTAssertNil(videoParameters["previewPhoto"], .missingPreviewPhotoParameter)
}
func testPreviewPhotoBridgeParameters() throws {
content = .withPreviewPhoto
let parameters = content.addParameters(originalParameters, options: [])
let videoParameters = try getVideoParameters(from: parameters, message: .previewPhotoParameter)
let photo = try XCTUnwrap(videoParameters["previewPhoto"] as? SharePhoto, .previewPhotoParameter)
XCTAssertIdentical(photo, content.video.previewPhoto, .previewPhotoParameter)
}
// MARK: - Helpers
private func validateOriginalParameters(
in parameters: [String: Any],
file: StaticString = #file,
line: UInt = #line
) throws {
let value = try XCTUnwrap(parameters["original"] as? String, .maintainsOriginalParameters, file: file, line: line)
XCTAssertEqual(value, "value", .maintainsOriginalParameters, file: file, line: line)
}
private func getVideoParameters(
from parameters: [String: Any],
message: String,
file: StaticString = #file,
line: UInt = #line
) throws -> [String: Any] {
try XCTUnwrap(parameters["video"] as? [String: Any], message, file: file, line: line)
}
private func validateEmptyVideoParameters(
in parameters: [String: Any],
_ message: String,
file: StaticString = #file,
line: UInt = #line
) throws {
let videoParameters = try XCTUnwrap(parameters["video"] as? [String: Any], message, file: file, line: line)
XCTAssertTrue(videoParameters.isEmpty, message, file: file, line: line)
}
}
// MARK: - Assumptions
fileprivate extension String {
static let usesShareUtilityAsShareValidatorByDefault = """
The default share validator dependency should be the _ShareUtility type
"""
static let usesPHImageManagerAsMediaLibrarySearcherByDefault = """
The default media library searching dependency should be the default PHImageManager
"""
static let usesCustomShareValidator = "The share validator dependency should be configurable"
static let usesCustomMediaLibrarySearcher = "The media library searching dependency should be configurable"
static let hasVideo = "A share video content has a video"
static let hasContentURL = "A share video content can have a content URL"
static let hasHashtag = "A share video content can have a hashtag"
static let hasPeopleIDs = "A share video content has people IDs"
static let hasPlaceID = "A share video content can have a place ID"
static let hasRef = "A share video content can have a ref"
static let hasPageID = "A share video content can have a page ID"
static let hasShareUUID = "A share video content has a share UUID"
static let validationValidatesVideo = """
Validating a share video content should validate its video using its validator
"""
static let maintainsOriginalParameters = "A share video content should maintain the original parameters"
static let invalidVideoParameters = "An invalid video should not provide any parameters"
static let assetIdentifierParameter = """
A video with an asset source should provide the asset's identifier when using the video asset option
"""
static let assetURLParameter = """
A video with an asset source should provide the asset's URL when not using the video asset option
"""
static let dataVideoParametersWithoutDataOption = "A video with a data source should not provide any parameters"
static let dataVideoParametersWithDataOption = "A video with a data source should provide the video's data"
static let urlVideoWithAssetURLParameters = "A video with an asset source should provide the asset's data"
static let fileURLVideoParametersWithoutDataOption = """
A video with a file URL should not provide any parameters without the video data option
"""
static let fileURLVideoParametersWithDataOption = "A video with a file URL should provide the video's data"
static let remoteURLVideoParameters = "A video with a remote URL source should not provide any parameters"
static let missingPreviewPhotoParameter = """
A video without a preview photo should not include a preview photo parameter
"""
static let previewPhotoParameter = "A video with a preview photo should include a preview photo parameter"
}
// MARK: - Test values
fileprivate extension URL {
// swiftlint:disable force_unwrapping
static let content = URL(string: "https://developers.facebook.com/")!
static let remote = URL(string: "https://developers.facebook.com/")!
static let asset = URL(
string: "assets-library://asset/asset.mp4?id=86C6970B-1266-42D0-91E8-4E68127D3864&ext=mp4"
)!
static let local = Bundle(for: ShareVideoContentTests.self)
.url(forResource: "dog-or-muffin", withExtension: "jpeg")!
static let remotePhoto = URL(string: "https://fbstatic-a.akamaihd.net/rsrc.php/v2/yC/r/YRwxe7CPWSs.png")!
// swiftlint:enable force_unwrapping
}
fileprivate extension Hashtag {
static let sample = Hashtag("#sample")
}
fileprivate extension String {
static let placeID = "141887372509674"
static let ref = "sample-ref"
static let pageID = "12345"
}
fileprivate extension Array where Element == String {
static let peopleIDs = ["person1", "person2"]
}
fileprivate extension Data {
static let video = try! Data(contentsOf: .local, options: .mappedIfSafe) // swiftlint:disable:this force_try
}
fileprivate extension ShareVideo {
static let plain = ShareVideo()
static let withAssetSource = ShareVideo(videoAsset: .testAsset)
static let withDataSource = ShareVideo(data: .video)
static let withRemoteURLSource = ShareVideo(videoURL: .remote)
static let withAssetURLSource = ShareVideo(videoURL: .asset)
static let withFileURLSource = ShareVideo(videoURL: .local)
}
fileprivate extension PHAsset {
static let testAsset: TestPHAsset = {
let asset = TestPHAsset()
asset.stubbedLocalIdentifier = "identifier"
return asset
}()
}
fileprivate extension SharePhoto {
static let sample = SharePhoto(imageURL: .remotePhoto, isUserGenerated: true)
}
fileprivate extension ShareVideoContent {
static let allProperties: ShareVideoContent = {
let content = ShareVideoContent()
content.video = .plain
content.contentURL = .content
content.hashtag = .sample
content.peopleIDs = .peopleIDs
content.placeID = .placeID
content.ref = .ref
content.pageID = .pageID
return content
}()
static func valid(_ video: ShareVideo) -> ShareVideoContent {
let content = ShareVideoContent()
content.video = video
return content
}
static let withoutPreviewPhoto = ShareVideoContent()
static let withPreviewPhoto: ShareVideoContent = {
let video = ShareVideo()
video.previewPhoto = .sample
let content = ShareVideoContent()
content.video = video
return content
}()
}