diff --git a/.gitignore b/.gitignore index 4320c73..5add839 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ DerivedData/ Aespa-test-env/cuckoo_generator Aespa-test-env/run + +Tests/Cuckoo/cuckoo_generator + +Tests/Cuckoo/run diff --git a/README.md b/README.md index 0ab9412..781b371 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,6 @@ aespaSession.videoFilePublisher } .store(in: &subsriptions) ``` -Based on the code provided, here is a draft for a README file that includes this functionality: ## SwiftUI Integration diff --git a/Sources/Aespa/AespaOption.swift b/Sources/Aespa/AespaOption.swift index 5ba145e..14ae8a6 100644 --- a/Sources/Aespa/AespaOption.swift +++ b/Sources/Aespa/AespaOption.swift @@ -48,17 +48,26 @@ public extension AespaOption { struct Asset { /// The name of the album where recorded videos will be saved. let albumName: String + + /// A `Boolean` flag that determines to use in-memory cache for `VideoFile` + /// + /// It's set `true` by default. + let useVideoFileCache: Bool + /// The file extension for the recorded videos. let fileNameHandler: FileNamingRule + /// The rule for naming video files. let fileExtension: String init( albumName: String, + useVideoFileCache: Bool = true, fileExtension: FileExtension = .mp4, fileNameHandler: @escaping FileNamingRule = FileNamingRulePreset.Timestamp().rule ) { self.albumName = albumName + self.useVideoFileCache = useVideoFileCache self.fileExtension = fileExtension.rawValue self.fileNameHandler = fileNameHandler } @@ -68,6 +77,8 @@ public extension AespaOption { struct Session { /// A Boolean value that determines whether video orientation should be automatic. var autoVideoOrientationEnabled: Bool = true + /// An `AVCaptureDevice.DeviceType` value that determines camera device. If not specified, the device is automatically selected. + var cameraDevicePreference: AVCaptureDevice.DeviceType? } /// `Log` provides an option for enabling or disabling logging. @@ -89,7 +100,7 @@ public extension AespaOption { /// Creates a `Timestamp` file naming rule. init() { formatter = DateFormatter() - formatter.dateFormat = "yyyy_MM_dd_HH-mm" + formatter.dateFormat = "yyyy_MM_dd_HH_mm_ss" } } diff --git a/Sources/Aespa/AespaSession.swift b/Sources/Aespa/AespaSession.swift index f483d6e..0ed9dc2 100644 --- a/Sources/Aespa/AespaSession.swift +++ b/Sources/Aespa/AespaSession.swift @@ -22,7 +22,7 @@ open class AespaSession { private let option: AespaOption private let coreSession: AespaCoreSession private let recorder: AespaCoreRecorder - private let fileManager: FileManager + private let fileManager: AespaCoreFileManager private let albumManager: AespaCoreAlbumManager private let videoFileBufferSubject: CurrentValueSubject?, Never> @@ -40,7 +40,7 @@ open class AespaSession { option: option, session: session, recorder: .init(core: session), - fileManager: .default, + fileManager: .init(enableCaching: option.asset.useVideoFileCache), albumManager: .init(albumName: option.asset.albumName) ) } @@ -49,7 +49,7 @@ open class AespaSession { option: AespaOption, session: AespaCoreSession, recorder: AespaCoreRecorder, - fileManager: FileManager, + fileManager: AespaCoreFileManager, albumManager: AespaCoreAlbumManager ) { self.option = option @@ -64,8 +64,8 @@ open class AespaSession { self.previewLayer = AVCaptureVideoPreviewLayer(session: session) // Add first video file to buffer if it exists - if let firstVideoFile = fetch(count: 1).first { - self.videoFileBufferSubject.send(.success(firstVideoFile)) + if let firstVideoFile = fileManager.fetch(albumName: option.asset.albumName, count: 1).first { + videoFileBufferSubject.send(.success(firstVideoFile)) } } @@ -105,17 +105,12 @@ open class AespaSession { /// /// - Returns: `VideoFile` wrapped in a `Result` type. public var videoFilePublisher: AnyPublisher, Never> { - recorder.fileIOResultPublihser.map { status in - switch status { - case .success(let url): - return .success(VideoFileGenerator.generate(with: url)) - case .failure(let error): + videoFileBufferSubject.handleEvents(receiveOutput: { status in + if case .failure(let error) = status { Logger.log(error: error) - return .failure(error) } - } - .merge(with: videoFileBufferSubject.eraseToAnyPublisher()) - .compactMap { $0 } + }) + .compactMap({ $0 }) .eraseToAnyPublisher() } @@ -168,7 +163,6 @@ open class AespaSession { } } } - /// Mutes the audio input for the video recording session. /// @@ -323,7 +317,7 @@ open class AespaSession { public func startRecordingWithError() throws { let fileName = option.asset.fileNameHandler() let filePath = try VideoFilePathProvider.requestFilePath( - from: fileManager, + from: fileManager.systemFileManager, directoryName: option.asset.albumName, fileName: fileName) @@ -342,9 +336,12 @@ open class AespaSession { /// - Throws: `AespaError` if stopping the recording fails. public func stopRecording() async throws -> VideoFile { let videoFilePath = try await recorder.stopRecording() - try await self.albumManager.addToAlbum(filePath: videoFilePath) + let videoFile = VideoFileGenerator.generate(with: videoFilePath, date: Date()) + + try await albumManager.addToAlbum(filePath: videoFilePath) + videoFileBufferSubject.send(.success(videoFile)) - return VideoFileGenerator.generate(with: videoFilePath) + return videoFile } /// Mutes the audio input for the video recording session. @@ -387,6 +384,8 @@ open class AespaSession { /// Sets the camera position for the video recording session. /// + /// It refers to `AespaOption.Session.cameraDevicePreference` when choosing the camera device. + /// /// - Parameter position: An `AVCaptureDevice.Position` value indicating the camera position to be set. /// /// - Throws: `AespaError` if the session fails to run the tuner. @@ -394,7 +393,8 @@ open class AespaSession { /// - Returns: `AespaSession`, for chaining calls. @discardableResult public func setPositionWithError(to position: AVCaptureDevice.Position) throws -> AespaSession { - let tuner = CameraPositionTuner(position: position) + let tuner = CameraPositionTuner(position: position, + devicePreference: option.session.cameraDevicePreference) try coreSession.run(tuner) return self } @@ -479,9 +479,9 @@ open class AespaSession { /// If the limit is set to 0 (default), all recorded video files will be fetched. /// - Returns: An array of `VideoFile` instances. public func fetchVideoFiles(limit: Int = 0) -> [VideoFile] { - return fetch(count: limit) + return fileManager.fetch(albumName: option.asset.albumName, count: limit) } - + /// Checks if essential conditions to start recording are satisfied. /// This includes checking for capture authorization, if the session is running, /// if there is an existing connection and if a device is attached. @@ -526,61 +526,3 @@ extension AespaSession { try coreSession.run(tuner) } } - -private extension AespaSession { - /// If `count` is `0`, return all existing files - func fetch(count: Int) async -> [VideoFile] { - guard count >= 0 else { return [] } - - return await withCheckedContinuation { [weak self] continuation in - guard let self else { return } - - DispatchQueue.global(qos: .utility).async { - do { - let directoryPath = try VideoFilePathProvider.requestDirectoryPath(from: self.fileManager, - name: self.option.asset.albumName) - - let filePaths = try self.fileManager.contentsOfDirectory(atPath: directoryPath.path) - let filePathPrefix = count == 0 ? filePaths : Array(filePaths.prefix(count)) - - let files = filePathPrefix - .map { name -> URL in - return directoryPath.appendingPathComponent(name) - } - .map { filePath -> VideoFile in - return VideoFileGenerator.generate(with: filePath) - } - - continuation.resume(returning: files) - } catch let error { - Logger.log(error: error) - continuation.resume(returning: []) - } - } - } - } - - /// If `count` is `0`, return all existing files - func fetch(count: Int) -> [VideoFile] { - guard count >= 0 else { return [] } - - do { - let directoryPath = try VideoFilePathProvider.requestDirectoryPath(from: fileManager, - name: option.asset.albumName) - - let filePaths = try fileManager.contentsOfDirectory(atPath: directoryPath.path) - let filePathPrefix = count == 0 ? filePaths : Array(filePaths.prefix(count)) - - return filePathPrefix - .map { name -> URL in - return directoryPath.appendingPathComponent(name) - } - .map { filePath -> VideoFile in - return VideoFileGenerator.generate(with: filePath) - } - } catch let error { - Logger.log(error: error) - return [] - } - } -} diff --git a/Sources/Aespa/Core/AespaCoreFileManager.swift b/Sources/Aespa/Core/AespaCoreFileManager.swift new file mode 100644 index 0000000..394a7e9 --- /dev/null +++ b/Sources/Aespa/Core/AespaCoreFileManager.swift @@ -0,0 +1,42 @@ +// +// AespaCoreFileManager.swift +// +// +// Created by 이영빈 on 2023/06/13. +// + +import Foundation + +class AespaCoreFileManager { + private var videoFileProxyDictionary: [String: VideoFileCachingProxy] + private let enableCaching: Bool + + let systemFileManager: FileManager + + init( + enableCaching: Bool, + fileManager: FileManager = .default + ) { + videoFileProxyDictionary = [:] + self.enableCaching = enableCaching + self.systemFileManager = fileManager + } + + /// If `count` is `0`, return all existing files + func fetch(albumName: String, count: Int) -> [VideoFile] { + guard count >= 0 else { return [] } + + guard let proxy = videoFileProxyDictionary[albumName] else { + videoFileProxyDictionary[albumName] = VideoFileCachingProxy( + albumName: albumName, + enableCaching: enableCaching, + fileManager: systemFileManager) + + return fetch(albumName: albumName, count: count) + } + + let files = proxy.fetch(count: count) + Logger.log(message: "\(files.count) Video files fetched") + return files + } +} diff --git a/Sources/Aespa/Core/AespaCoreRecorder.swift b/Sources/Aespa/Core/AespaCoreRecorder.swift index 05397ae..e9f8829 100644 --- a/Sources/Aespa/Core/AespaCoreRecorder.swift +++ b/Sources/Aespa/Core/AespaCoreRecorder.swift @@ -31,10 +31,6 @@ class AespaCoreRecorder: NSObject { } extension AespaCoreRecorder { - var fileIOResultPublihser: AnyPublisher, Never> { - return self.fileIOResultSubject.eraseToAnyPublisher() - } - func startRecording(in filePath: URL) throws { try run(processor: StartRecordProcessor(filePath: filePath, delegate: self)) } diff --git a/Sources/Aespa/Core/AespaCoreSession+AespaCoreSessionRepresentable.swift b/Sources/Aespa/Core/AespaCoreSession+AespaCoreSessionRepresentable.swift index 51f0b87..bab9173 100644 --- a/Sources/Aespa/Core/AespaCoreSession+AespaCoreSessionRepresentable.swift +++ b/Sources/Aespa/Core/AespaCoreSession+AespaCoreSessionRepresentable.swift @@ -55,10 +55,10 @@ public protocol AespaCoreSessionRepresentable { /// Sets the position of the camera. /// Throws an error if the operation fails. - func setCameraPosition(to position: AVCaptureDevice.Position) throws + func setCameraPosition(to position: AVCaptureDevice.Position, device deviceType: AVCaptureDevice.DeviceType?) throws /// Sets the video quality preset. - func setVideoQuality(to preset: AVCaptureSession.Preset) + func setVideoQuality(to preset: AVCaptureSession.Preset) throws } extension AespaCoreSession: AespaCoreSessionRepresentable { @@ -159,14 +159,22 @@ extension AespaCoreSession: AespaCoreSessionRepresentable { } // MARK: - Option related - func setCameraPosition(to position: AVCaptureDevice.Position) throws { + func setCameraPosition(to position: AVCaptureDevice.Position,device deviceType: AVCaptureDevice.DeviceType?) throws { let session = self if let videoDeviceInput { session.removeInput(videoDeviceInput) } - guard let device = position.chooseBestCamera else { + let device: AVCaptureDevice + if + let deviceType, + let captureDeivce = AVCaptureDevice.default(deviceType, for: .video, position: position) + { + device = captureDeivce + } else if let bestDevice = position.chooseBestCamera { + device = bestDevice + } else { throw AespaError.device(reason: .invalid) } diff --git a/Sources/Aespa/Data/VideoFile.swift b/Sources/Aespa/Data/VideoFile.swift new file mode 100644 index 0000000..864fd74 --- /dev/null +++ b/Sources/Aespa/Data/VideoFile.swift @@ -0,0 +1,53 @@ +// +// VideoFile.swift +// +// +// Created by 이영빈 on 2023/06/13. +// + +import UIKit +import SwiftUI +import AVFoundation + +/// `VideoFile` represents a video file with its associated metadata. +/// +/// This struct holds information about the video file, including a unique identifier (`id`), +/// the path to the video file (`path`), and an optional thumbnail image (`thumbnail`) +/// generated from the video. +public struct VideoFile: Equatable { + /// A `Date` value keeps the date it's generated + public let generatedDate: Date + + /// The path to the video file. + public let path: URL + + /// An optional thumbnail generated from the video with `UIImage` type. + /// This will be `nil` if the thumbnail could not be generated for some reason. + public var thumbnail: UIImage? +} + +/// UI related extension methods +public extension VideoFile { + + /// An optional thumbnail generated from the video with SwiftUI `Image` type. + /// This will be `nil` if the thumbnail could not be generated for some reason. + var thumbnailImage: Image? { + if let thumbnail { + return Image(uiImage: thumbnail) + } + + return nil + } +} + +extension VideoFile: Identifiable { + public var id: URL { + self.path + } +} + +extension VideoFile: Comparable { + public static func < (lhs: VideoFile, rhs: VideoFile) -> Bool { + lhs.generatedDate > rhs.generatedDate + } +} diff --git a/Sources/Aespa/Data/VideoFileCachingProxy.swift b/Sources/Aespa/Data/VideoFileCachingProxy.swift new file mode 100644 index 0000000..e1a9430 --- /dev/null +++ b/Sources/Aespa/Data/VideoFileCachingProxy.swift @@ -0,0 +1,118 @@ +// +// VideoFileCachingProxy.swift +// +// +// Created by 이영빈 on 2023/06/14. +// + +import Foundation + +class VideoFileCachingProxy { + private let albumName: String + private let cacheEnabled: Bool + + private let fileManager: FileManager + + private var cache: [URL: VideoFile] = [:] + private var lastModificationDate: Date? + + init(albumName: String, enableCaching: Bool, fileManager: FileManager) { + self.albumName = albumName + self.cacheEnabled = enableCaching + self.fileManager = fileManager + + DispatchQueue.global().async { + self.updateCache() + } + } + + /// If `count` is `0`, return all existing files + func fetch(count: Int) -> [VideoFile] { + guard + let albumDirectory = try? VideoFilePathProvider.requestDirectoryPath(from: fileManager, + name: albumName) + else { + return [] + } + + guard cacheEnabled else { + invalidateCache() + return fetchFile(from: albumDirectory, count: count).sorted() + } + + guard + let directoryAttributes = try? fileManager.attributesOfItem(atPath: albumDirectory.path), + let currentModificationDate = directoryAttributes[.modificationDate] as? Date + else { + return [] + } + + // Check if the directory has been modified since last fetch + if let lastModificationDate = self.lastModificationDate, + lastModificationDate == currentModificationDate { + return fetchSortedFiles(count: count) + } + + // Update cache and lastModificationDate + updateCache() + self.lastModificationDate = currentModificationDate + + return fetchSortedFiles(count: count) + } + + // Invalidate the cache if needed, for example when a file is added or removed + func invalidateCache() { + cache.removeAll() + lastModificationDate = nil + } +} + + +private extension VideoFileCachingProxy { + func updateCache() { + guard + let albumDirectory = try? VideoFilePathProvider.requestDirectoryPath(from: fileManager, name: albumName), + let filePaths = try? fileManager.contentsOfDirectory(atPath: albumDirectory.path) + else { + Logger.log(message: "Cannot access to saved video file") + return + } + + var newCache: [URL: VideoFile] = [:] + filePaths.forEach { fileName in + let filePath = albumDirectory.appendingPathComponent(fileName) + if let cachedFile = cache[filePath] { + newCache[filePath] = cachedFile + } else if let videoFile = createVideoFile(for: filePath) { + newCache[filePath] = videoFile + } + } + cache = newCache + } + + func fetchFile(from albumDirectory: URL, count: Int) -> [VideoFile] { + guard count >= 0 else { return [] } + + let files = Array(cache.values) + let sortedFiles = files.sorted() + let prefixFiles = (count == 0) ? sortedFiles : Array(sortedFiles.prefix(count)) + return prefixFiles + } + + func createVideoFile(for filePath: URL) -> VideoFile? { + guard + let fileAttributes = try? fileManager.attributesOfItem(atPath: filePath.path), + let creationDate = fileAttributes[.creationDate] as? Date + else { + Logger.log(message: "Cannot access to saved video file") + return nil + } + + return VideoFileGenerator.generate(with: filePath, date: creationDate) + } + + func fetchSortedFiles(count: Int) -> [VideoFile] { + let files = cache.values.sorted() + return count == 0 ? files : Array(files.prefix(count)) + } +} diff --git a/Sources/Aespa/Tuner/Session/CameraPositionTuner.swift b/Sources/Aespa/Tuner/Session/CameraPositionTuner.swift index 5cb1b74..747d3ff 100644 --- a/Sources/Aespa/Tuner/Session/CameraPositionTuner.swift +++ b/Sources/Aespa/Tuner/Session/CameraPositionTuner.swift @@ -10,9 +10,15 @@ import AVFoundation struct CameraPositionTuner: AespaSessionTuning { let needTransaction = true var position: AVCaptureDevice.Position + var devicePreference: AVCaptureDevice.DeviceType? + + init(position: AVCaptureDevice.Position, devicePreference: AVCaptureDevice.DeviceType? = nil) { + self.position = position + self.devicePreference = devicePreference + } func tune(_ session: T) throws { - try session.setCameraPosition(to: position) + try session.setCameraPosition(to: position, device: devicePreference) } } diff --git a/Sources/Aespa/Tuner/Session/QualityTuner.swift b/Sources/Aespa/Tuner/Session/QualityTuner.swift index 9cb79e2..dc85bc3 100644 --- a/Sources/Aespa/Tuner/Session/QualityTuner.swift +++ b/Sources/Aespa/Tuner/Session/QualityTuner.swift @@ -11,7 +11,7 @@ struct QualityTuner: AespaSessionTuning { let needTransaction = true var videoQuality: AVCaptureSession.Preset - func tune(_ session: T) { - session.setVideoQuality(to: self.videoQuality) + func tune(_ session: T) throws { + try session.setVideoQuality(to: self.videoQuality) } } diff --git a/Sources/Aespa/Util/Extension/AVFoundation+Extension.swift b/Sources/Aespa/Util/Extension/AVFoundation+Extension.swift index c6f651d..f45aade 100644 --- a/Sources/Aespa/Util/Extension/AVFoundation+Extension.swift +++ b/Sources/Aespa/Util/Extension/AVFoundation+Extension.swift @@ -19,18 +19,37 @@ extension AVCaptureConnection { extension AVCaptureDevice.Position { var chooseBestCamera: AVCaptureDevice? { - let position = self + let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInTripleCamera, .builtInWideAngleCamera], + mediaType: .video, + position: self) + + // Sort the devices by resolution + let sortedDevices = discoverySession.devices.sorted { (device1, device2) -> Bool in + guard let maxResolution1 = device1.maxResolution, + let maxResolution2 = device2.maxResolution else { + return false + } + return maxResolution1 > maxResolution2 + } - if let device = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: position) { - return device - } else if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position) { - return device - } else { - return nil + // Return the device with the highest resolution, or nil if no devices were found + return sortedDevices.first + } +} + +fileprivate extension AVCaptureDevice { + var maxResolution: Double? { + var maxResolution: Double = 0 + for format in self.formats { + let dimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription) + let resolution = Double(dimensions.width * dimensions.height) + maxResolution = max(resolution, maxResolution) } + return maxResolution } } + extension AVCaptureDevice { func setZoomFactor(factor: CGFloat) { let device = self diff --git a/Sources/Aespa/Util/Extension/SwiftUI+Extension.swift b/Sources/Aespa/Util/Extension/SwiftUI+Extension.swift index 738c45a..accb14c 100644 --- a/Sources/Aespa/Util/Extension/SwiftUI+Extension.swift +++ b/Sources/Aespa/Util/Extension/SwiftUI+Extension.swift @@ -20,16 +20,6 @@ public extension AespaSession { } } -public extension VideoFile { - var thumbnailImage: Image? { - if let thumbnail { - return Image(uiImage: thumbnail) - } - - return nil - } -} - fileprivate struct AespaSwiftUIPreview: UIViewRepresentable { @StateObject var viewModel: AespaSwiftUIPreviewViewModel let gravity: AVLayerVideoGravity diff --git a/Sources/Aespa/Util/Video/File/VideoFileGenerator.swift b/Sources/Aespa/Util/Video/File/VideoFileGenerator.swift index 703b235..d45b0b5 100644 --- a/Sources/Aespa/Util/Video/File/VideoFileGenerator.swift +++ b/Sources/Aespa/Util/Video/File/VideoFileGenerator.swift @@ -8,43 +8,20 @@ import UIKit import AVFoundation -/// `VideoFile` represents a video file with its associated metadata. -/// -/// This struct holds information about the video file, including a unique identifier (`id`), -/// the path to the video file (`path`), and an optional thumbnail image (`thumbnail`) -/// generated from the video. -public struct VideoFile: Identifiable, Equatable { - /// A unique identifier for the video file. - public let id = UUID() - - /// The path to the video file. - public let path: URL - - /// An optional thumbnail image generated from the video. - /// This will be `nil` if the thumbnail could not be generated for some reason. - public let thumbnail: UIImage? -} - struct VideoFileGenerator { - static func generate(with path: URL) -> VideoFile { - let thumbnail = generateThumbnail(for: path) - - return VideoFile(path: path, thumbnail: thumbnail) - } -} - -private extension VideoFileGenerator { - static func generateVideoFile(of path: URL) -> VideoFile { - let thumbnail = generateThumbnail(for: path) - - return VideoFile(path: path, thumbnail: thumbnail) + + static func generate(with path: URL, date: Date) -> VideoFile { + return VideoFile( + generatedDate: date, + path: path, + thumbnail: VideoFileGenerator.generateThumbnail(for: path)) } static func generateThumbnail(for path: URL) -> UIImage? { let asset = AVURLAsset(url: path, options: nil) - let imageGenerator = AVAssetImageGenerator(asset: asset) imageGenerator.appliesPreferredTrackTransform = true + imageGenerator.maximumSize = .init(width: 250, height: 250) do { let cgImage = try imageGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil) diff --git a/Tests/Aespa-iOS-test.xcodeproj/project.pbxproj b/Tests/Aespa-iOS-test.xcodeproj/project.pbxproj index ff7cbf4..ba7a1dd 100644 --- a/Tests/Aespa-iOS-test.xcodeproj/project.pbxproj +++ b/Tests/Aespa-iOS-test.xcodeproj/project.pbxproj @@ -11,10 +11,10 @@ 07939F932A343B8D00DFA8BB /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07939F922A343B8D00DFA8BB /* ContentView.swift */; }; 07939F952A343B8E00DFA8BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 07939F942A343B8E00DFA8BB /* Assets.xcassets */; }; 07939F982A343B8E00DFA8BB /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 07939F972A343B8E00DFA8BB /* Preview Assets.xcassets */; }; - 07F359B12A34449B00F4EF16 /* Aespa in Frameworks */ = {isa = PBXBuildFile; productRef = 07F359B02A34449B00F4EF16 /* Aespa */; }; 07F359B82A347CA400F4EF16 /* Cuckoo in Frameworks */ = {isa = PBXBuildFile; productRef = 07F359B72A347CA400F4EF16 /* Cuckoo */; }; 07F359BA2A347DF600F4EF16 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F359B92A347DF600F4EF16 /* GeneratedMocks.swift */; }; 07F359BE2A3489C000F4EF16 /* SessionTunerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F359BD2A3489C000F4EF16 /* SessionTunerTests.swift */; }; + 9CA7CFF82A380673000B11B3 /* Aespa in Frameworks */ = {isa = PBXBuildFile; productRef = 9CA7CFF72A380673000B11B3 /* Aespa */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -42,9 +42,10 @@ 07939F972A343B8E00DFA8BB /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 07939F9D2A343B8E00DFA8BB /* Aespa-iOS-testTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Aespa-iOS-testTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 07939FA72A343B8E00DFA8BB /* Aespa-iOS-testUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Aespa-iOS-testUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 0795E11E2A35A0E9001AD4DC /* Aespa */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Aespa; path = ..; sourceTree = ""; }; 07F359B92A347DF600F4EF16 /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedMocks.swift; sourceTree = ""; }; 07F359BD2A3489C000F4EF16 /* SessionTunerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTunerTests.swift; sourceTree = ""; }; + 9CA7CFFA2A38069C000B11B3 /* Aespa */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Aespa; path = ..; sourceTree = ""; }; + 9CA7CFFB2A380754000B11B3 /* Aespa-iOS-test.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Aespa-iOS-test.xctestplan"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -53,7 +54,7 @@ buildActionMask = 2147483647; files = ( 07F359B82A347CA400F4EF16 /* Cuckoo in Frameworks */, - 07F359B12A34449B00F4EF16 /* Aespa in Frameworks */, + 9CA7CFF82A380673000B11B3 /* Aespa in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -77,7 +78,8 @@ 07939F842A343B8D00DFA8BB = { isa = PBXGroup; children = ( - 0795E11D2A35A0E9001AD4DC /* Packages */, + 9CA7CFFB2A380754000B11B3 /* Aespa-iOS-test.xctestplan */, + 9CA7CFF92A38069C000B11B3 /* Packages */, 07939F8F2A343B8D00DFA8BB /* Aespa-iOS-test */, 07939FA02A343B8E00DFA8BB /* Aespa-iOS-testTests */, 07939F8E2A343B8D00DFA8BB /* Products */, @@ -123,14 +125,6 @@ path = "Aespa-iOS-testTests"; sourceTree = ""; }; - 0795E11D2A35A0E9001AD4DC /* Packages */ = { - isa = PBXGroup; - children = ( - 0795E11E2A35A0E9001AD4DC /* Aespa */, - ); - name = Packages; - sourceTree = ""; - }; 0795E11F2A35A2FC001AD4DC /* Tuner */ = { isa = PBXGroup; children = ( @@ -154,6 +148,14 @@ name = Frameworks; sourceTree = ""; }; + 9CA7CFF92A38069C000B11B3 /* Packages */ = { + isa = PBXGroup; + children = ( + 9CA7CFFA2A38069C000B11B3 /* Aespa */, + ); + name = Packages; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -171,8 +173,8 @@ ); name = "Aespa-iOS-test"; packageProductDependencies = ( - 07F359B02A34449B00F4EF16 /* Aespa */, 07F359B72A347CA400F4EF16 /* Cuckoo */, + 9CA7CFF72A380673000B11B3 /* Aespa */, ); productName = "Aespa-iOS-test"; productReference = 07939F8D2A343B8D00DFA8BB /* Aespa-iOS-test.app */; @@ -637,15 +639,15 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 07F359B02A34449B00F4EF16 /* Aespa */ = { - isa = XCSwiftPackageProductDependency; - productName = Aespa; - }; 07F359B72A347CA400F4EF16 /* Cuckoo */ = { isa = XCSwiftPackageProductDependency; package = 07F359B62A347CA400F4EF16 /* XCRemoteSwiftPackageReference "Cuckoo" */; productName = Cuckoo; }; + 9CA7CFF72A380673000B11B3 /* Aespa */ = { + isa = XCSwiftPackageProductDependency; + productName = Aespa; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 07939F852A343B8D00DFA8BB /* Project object */; diff --git a/Tests/Aespa-iOS-test.xcodeproj/xcshareddata/xcschemes/Aespa-iOS-test.xcscheme b/Tests/Aespa-iOS-test.xcodeproj/xcshareddata/xcschemes/Aespa-iOS-test.xcscheme index 1f7e276..d0244b7 100644 --- a/Tests/Aespa-iOS-test.xcodeproj/xcshareddata/xcschemes/Aespa-iOS-test.xcscheme +++ b/Tests/Aespa-iOS-test.xcodeproj/xcshareddata/xcschemes/Aespa-iOS-test.xcscheme @@ -26,8 +26,13 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - shouldAutocreateTestPlan = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + ) -> Void) { return cuckoo_manager.call( """ - stopRecording() + stopRecording(_: @escaping (Result) -> Void) """, - parameters: (), - escapingParameters: (), + parameters: (completionHandler), + escapingParameters: (completionHandler), superclassCall: - super.stopRecording() + super.stopRecording(completionHandler) , - defaultCall: __defaultImplStub!.stopRecording()) + defaultCall: __defaultImplStub!.stopRecording(completionHandler)) } @@ -470,19 +470,19 @@ public class MockAespaSession: AespaSession, Cuckoo.ClassMock { - public override func stopRecordingWithError() throws { + public override func stopRecording() async throws -> VideoFile { - return try cuckoo_manager.callThrows( + return try await cuckoo_manager.callThrows( """ - stopRecordingWithError() throws + stopRecording() async throws -> VideoFile """, parameters: (), escapingParameters: (), superclassCall: - super.stopRecordingWithError() + await super.stopRecording() , - defaultCall: __defaultImplStub!.stopRecordingWithError()) + defaultCall: await __defaultImplStub!.stopRecording()) } @@ -650,19 +650,19 @@ public class MockAespaSession: AespaSession, Cuckoo.ClassMock { - public override func custom(_ tuner: T) throws { + public override func customize(_ tuner: T) throws { return try cuckoo_manager.callThrows( """ - custom(_: T) throws + customize(_: T) throws """, parameters: (tuner), escapingParameters: (tuner), superclassCall: - super.custom(tuner) + super.customize(tuner) , - defaultCall: __defaultImplStub!.custom(tuner)) + defaultCall: __defaultImplStub!.customize(tuner)) } @@ -771,11 +771,11 @@ public class MockAespaSession: AespaSession, Cuckoo.ClassMock { - func stopRecording() -> Cuckoo.ClassStubNoReturnFunction<()> { - let matchers: [Cuckoo.ParameterMatcher] = [] + func stopRecording(_ completionHandler: M1) -> Cuckoo.ClassStubNoReturnFunction<((Result) -> Void)> where M1.MatchedType == (Result) -> Void { + let matchers: [Cuckoo.ParameterMatcher<((Result) -> Void)>] = [wrap(matchable: completionHandler) { $0 }] return .init(stub: cuckoo_manager.createStub(for: MockAespaSession.self, method: """ - stopRecording() + stopRecording(_: @escaping (Result) -> Void) """, parameterMatchers: matchers)) } @@ -881,11 +881,11 @@ public class MockAespaSession: AespaSession, Cuckoo.ClassMock { - func stopRecordingWithError() -> Cuckoo.ClassStubNoReturnThrowingFunction<()> { + func stopRecording() -> Cuckoo.ClassStubThrowingFunction<(), VideoFile> { let matchers: [Cuckoo.ParameterMatcher] = [] return .init(stub: cuckoo_manager.createStub(for: MockAespaSession.self, method: """ - stopRecordingWithError() throws + stopRecording() async throws -> VideoFile """, parameterMatchers: matchers)) } @@ -980,11 +980,11 @@ public class MockAespaSession: AespaSession, Cuckoo.ClassMock { - func custom(_ tuner: M1) -> Cuckoo.ClassStubNoReturnThrowingFunction<(T)> where M1.MatchedType == T { + func customize(_ tuner: M1) -> Cuckoo.ClassStubNoReturnThrowingFunction<(T)> where M1.MatchedType == T { let matchers: [Cuckoo.ParameterMatcher<(T)>] = [wrap(matchable: tuner) { $0 }] return .init(stub: cuckoo_manager.createStub(for: MockAespaSession.self, method: """ - custom(_: T) throws + customize(_: T) throws """, parameterMatchers: matchers)) } @@ -1084,11 +1084,11 @@ public class MockAespaSession: AespaSession, Cuckoo.ClassMock { @discardableResult - func stopRecording() -> Cuckoo.__DoNotUse<(), Void> { - let matchers: [Cuckoo.ParameterMatcher] = [] + func stopRecording(_ completionHandler: M1) -> Cuckoo.__DoNotUse<((Result) -> Void), Void> where M1.MatchedType == (Result) -> Void { + let matchers: [Cuckoo.ParameterMatcher<((Result) -> Void)>] = [wrap(matchable: completionHandler) { $0 }] return cuckoo_manager.verify( """ - stopRecording() + stopRecording(_: @escaping (Result) -> Void) """, callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } @@ -1204,11 +1204,11 @@ public class MockAespaSession: AespaSession, Cuckoo.ClassMock { @discardableResult - func stopRecordingWithError() -> Cuckoo.__DoNotUse<(), Void> { + func stopRecording() -> Cuckoo.__DoNotUse<(), VideoFile> { let matchers: [Cuckoo.ParameterMatcher] = [] return cuckoo_manager.verify( """ - stopRecordingWithError() throws + stopRecording() async throws -> VideoFile """, callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } @@ -1312,11 +1312,11 @@ public class MockAespaSession: AespaSession, Cuckoo.ClassMock { @discardableResult - func custom(_ tuner: M1) -> Cuckoo.__DoNotUse<(T), Void> where M1.MatchedType == T { + func customize(_ tuner: M1) -> Cuckoo.__DoNotUse<(T), Void> where M1.MatchedType == T { let matchers: [Cuckoo.ParameterMatcher<(T)>] = [wrap(matchable: tuner) { $0 }] return cuckoo_manager.verify( """ - custom(_: T) throws + customize(_: T) throws """, callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } @@ -1432,7 +1432,7 @@ public class AespaSessionStub: AespaSession { - public override func stopRecording() { + public override func stopRecording(_ completionHandler: @escaping (Result) -> Void) { return DefaultValueRegistry.defaultValue(for: (Void).self) } @@ -1512,8 +1512,8 @@ public class AespaSessionStub: AespaSession { - public override func stopRecordingWithError() throws { - return DefaultValueRegistry.defaultValue(for: (Void).self) + public override func stopRecording() async throws -> VideoFile { + return DefaultValueRegistry.defaultValue(for: (VideoFile).self) } @@ -1584,7 +1584,7 @@ public class AespaSessionStub: AespaSession { - public override func custom(_ tuner: T) throws { + public override func customize(_ tuner: T) throws { return DefaultValueRegistry.defaultValue(for: (Void).self) } @@ -1611,7 +1611,7 @@ public class AespaSessionStub: AespaSession { -// MARK: - Mocks generated from file: ../../Sources/Aespa/Core/AespaCoreAlbumManager.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Core/AespaCoreAlbumManager.swift at 2023-06-13 08:25:34 +0000 // // AespaCoreAlbumManager.swift @@ -1744,7 +1744,139 @@ import Photos -// MARK: - Mocks generated from file: ../../Sources/Aespa/Core/AespaCoreRecorder.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Core/AespaCoreFileManager.swift at 2023-06-13 08:25:34 +0000 + +// +// AespaCoreFileManager.swift +// +// +// Created by 이영빈 on 2023/06/13 +import Cuckoo +@testable import Aespa + +import Foundation + + + + + + + class MockAespaCoreFileManager: AespaCoreFileManager, Cuckoo.ClassMock { + + typealias MocksType = AespaCoreFileManager + + typealias Stubbing = __StubbingProxy_AespaCoreFileManager + typealias Verification = __VerificationProxy_AespaCoreFileManager + + let cuckoo_manager = Cuckoo.MockManager.preconfiguredManager ?? Cuckoo.MockManager(hasParent: true) + + + private var __defaultImplStub: AespaCoreFileManager? + + func enableDefaultImplementation(_ stub: AespaCoreFileManager) { + __defaultImplStub = stub + cuckoo_manager.enableDefaultStubImplementation() + } + + + + + + + + + + + override func fetch(albumName: String, count: Int) -> [VideoFile] { + + return cuckoo_manager.call( + """ + fetch(albumName: String, count: Int) -> [VideoFile] + """, + parameters: (albumName, count), + escapingParameters: (albumName, count), + superclassCall: + + super.fetch(albumName: albumName, count: count) + , + defaultCall: __defaultImplStub!.fetch(albumName: albumName, count: count)) + + } + + + + struct __StubbingProxy_AespaCoreFileManager: Cuckoo.StubbingProxy { + private let cuckoo_manager: Cuckoo.MockManager + + init(manager: Cuckoo.MockManager) { + self.cuckoo_manager = manager + } + + + + + func fetch(albumName: M1, count: M2) -> Cuckoo.ClassStubFunction<(String, Int), [VideoFile]> where M1.MatchedType == String, M2.MatchedType == Int { + let matchers: [Cuckoo.ParameterMatcher<(String, Int)>] = [wrap(matchable: albumName) { $0.0 }, wrap(matchable: count) { $0.1 }] + return .init(stub: cuckoo_manager.createStub(for: MockAespaCoreFileManager.self, method: + """ + fetch(albumName: String, count: Int) -> [VideoFile] + """, parameterMatchers: matchers)) + } + + + } + + struct __VerificationProxy_AespaCoreFileManager: Cuckoo.VerificationProxy { + private let cuckoo_manager: Cuckoo.MockManager + private let callMatcher: Cuckoo.CallMatcher + private let sourceLocation: Cuckoo.SourceLocation + + init(manager: Cuckoo.MockManager, callMatcher: Cuckoo.CallMatcher, sourceLocation: Cuckoo.SourceLocation) { + self.cuckoo_manager = manager + self.callMatcher = callMatcher + self.sourceLocation = sourceLocation + } + + + + + + + @discardableResult + func fetch(albumName: M1, count: M2) -> Cuckoo.__DoNotUse<(String, Int), [VideoFile]> where M1.MatchedType == String, M2.MatchedType == Int { + let matchers: [Cuckoo.ParameterMatcher<(String, Int)>] = [wrap(matchable: albumName) { $0.0 }, wrap(matchable: count) { $0.1 }] + return cuckoo_manager.verify( + """ + fetch(albumName: String, count: Int) -> [VideoFile] + """, callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + + + } +} + + + class AespaCoreFileManagerStub: AespaCoreFileManager { + + + + + + + + + override func fetch(albumName: String, count: Int) -> [VideoFile] { + return DefaultValueRegistry.defaultValue(for: ([VideoFile]).self) + } + + +} + + + + + +// MARK: - Mocks generated from file: ../../Sources/Aespa/Core/AespaCoreRecorder.swift at 2023-06-13 08:25:34 +0000 // // AespaCoreRecorder.swift @@ -1878,7 +2010,7 @@ import Foundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Core/AespaCoreSession+AespaCoreSessionRepresentable.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Core/AespaCoreSession+AespaCoreSessionRepresentable.swift at 2023-06-13 08:25:34 +0000 // // AespaCoreSession + AespaCoreSessionRepresentable.swift @@ -1920,14 +2052,14 @@ public class MockAespaCoreSessionRepresentable: AespaCoreSessionRepresentable, C - public var session: AVCaptureSession { + public var avCaptureSession: AVCaptureSession { get { - return cuckoo_manager.getter("session", + return cuckoo_manager.getter("avCaptureSession", superclassCall: Cuckoo.MockManager.crashOnProtocolSuperclassCall() , - defaultCall: __defaultImplStub!.session) + defaultCall: __defaultImplStub!.avCaptureSession) } } @@ -2160,19 +2292,19 @@ public class MockAespaCoreSessionRepresentable: AespaCoreSessionRepresentable, C - public func setCameraPosition(to position: AVCaptureDevice.Position) throws { + public func setCameraPosition(to position: AVCaptureDevice.Position, device deviceType: AVCaptureDevice.DeviceType?) throws { return try cuckoo_manager.callThrows( """ - setCameraPosition(to: AVCaptureDevice.Position) throws + setCameraPosition(to: AVCaptureDevice.Position, device: AVCaptureDevice.DeviceType?) throws """, - parameters: (position), - escapingParameters: (position), + parameters: (position, deviceType), + escapingParameters: (position, deviceType), superclassCall: Cuckoo.MockManager.crashOnProtocolSuperclassCall() , - defaultCall: __defaultImplStub!.setCameraPosition(to: position)) + defaultCall: __defaultImplStub!.setCameraPosition(to: position, device: deviceType)) } @@ -2180,11 +2312,11 @@ public class MockAespaCoreSessionRepresentable: AespaCoreSessionRepresentable, C - public func setVideoQuality(to preset: AVCaptureSession.Preset) { + public func setVideoQuality(to preset: AVCaptureSession.Preset) throws { - return cuckoo_manager.call( + return try cuckoo_manager.callThrows( """ - setVideoQuality(to: AVCaptureSession.Preset) + setVideoQuality(to: AVCaptureSession.Preset) throws """, parameters: (preset), escapingParameters: (preset), @@ -2207,8 +2339,8 @@ public class MockAespaCoreSessionRepresentable: AespaCoreSessionRepresentable, C - var session: Cuckoo.ProtocolToBeStubbedReadOnlyProperty { - return .init(manager: cuckoo_manager, name: "session") + var avCaptureSession: Cuckoo.ProtocolToBeStubbedReadOnlyProperty { + return .init(manager: cuckoo_manager, name: "avCaptureSession") } @@ -2327,22 +2459,22 @@ public class MockAespaCoreSessionRepresentable: AespaCoreSessionRepresentable, C - func setCameraPosition(to position: M1) -> Cuckoo.ProtocolStubNoReturnThrowingFunction<(AVCaptureDevice.Position)> where M1.MatchedType == AVCaptureDevice.Position { - let matchers: [Cuckoo.ParameterMatcher<(AVCaptureDevice.Position)>] = [wrap(matchable: position) { $0 }] + func setCameraPosition(to position: M1, device deviceType: M2) -> Cuckoo.ProtocolStubNoReturnThrowingFunction<(AVCaptureDevice.Position, AVCaptureDevice.DeviceType?)> where M1.MatchedType == AVCaptureDevice.Position, M2.OptionalMatchedType == AVCaptureDevice.DeviceType { + let matchers: [Cuckoo.ParameterMatcher<(AVCaptureDevice.Position, AVCaptureDevice.DeviceType?)>] = [wrap(matchable: position) { $0.0 }, wrap(matchable: deviceType) { $0.1 }] return .init(stub: cuckoo_manager.createStub(for: MockAespaCoreSessionRepresentable.self, method: """ - setCameraPosition(to: AVCaptureDevice.Position) throws + setCameraPosition(to: AVCaptureDevice.Position, device: AVCaptureDevice.DeviceType?) throws """, parameterMatchers: matchers)) } - func setVideoQuality(to preset: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(AVCaptureSession.Preset)> where M1.MatchedType == AVCaptureSession.Preset { + func setVideoQuality(to preset: M1) -> Cuckoo.ProtocolStubNoReturnThrowingFunction<(AVCaptureSession.Preset)> where M1.MatchedType == AVCaptureSession.Preset { let matchers: [Cuckoo.ParameterMatcher<(AVCaptureSession.Preset)>] = [wrap(matchable: preset) { $0 }] return .init(stub: cuckoo_manager.createStub(for: MockAespaCoreSessionRepresentable.self, method: """ - setVideoQuality(to: AVCaptureSession.Preset) + setVideoQuality(to: AVCaptureSession.Preset) throws """, parameterMatchers: matchers)) } @@ -2363,8 +2495,8 @@ public class MockAespaCoreSessionRepresentable: AespaCoreSessionRepresentable, C - var session: Cuckoo.VerifyReadOnlyProperty { - return .init(manager: cuckoo_manager, name: "session", callMatcher: callMatcher, sourceLocation: sourceLocation) + var avCaptureSession: Cuckoo.VerifyReadOnlyProperty { + return .init(manager: cuckoo_manager, name: "avCaptureSession", callMatcher: callMatcher, sourceLocation: sourceLocation) } @@ -2492,11 +2624,11 @@ public class MockAespaCoreSessionRepresentable: AespaCoreSessionRepresentable, C @discardableResult - func setCameraPosition(to position: M1) -> Cuckoo.__DoNotUse<(AVCaptureDevice.Position), Void> where M1.MatchedType == AVCaptureDevice.Position { - let matchers: [Cuckoo.ParameterMatcher<(AVCaptureDevice.Position)>] = [wrap(matchable: position) { $0 }] + func setCameraPosition(to position: M1, device deviceType: M2) -> Cuckoo.__DoNotUse<(AVCaptureDevice.Position, AVCaptureDevice.DeviceType?), Void> where M1.MatchedType == AVCaptureDevice.Position, M2.OptionalMatchedType == AVCaptureDevice.DeviceType { + let matchers: [Cuckoo.ParameterMatcher<(AVCaptureDevice.Position, AVCaptureDevice.DeviceType?)>] = [wrap(matchable: position) { $0.0 }, wrap(matchable: deviceType) { $0.1 }] return cuckoo_manager.verify( """ - setCameraPosition(to: AVCaptureDevice.Position) throws + setCameraPosition(to: AVCaptureDevice.Position, device: AVCaptureDevice.DeviceType?) throws """, callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } @@ -2508,7 +2640,7 @@ public class MockAespaCoreSessionRepresentable: AespaCoreSessionRepresentable, C let matchers: [Cuckoo.ParameterMatcher<(AVCaptureSession.Preset)>] = [wrap(matchable: preset) { $0 }] return cuckoo_manager.verify( """ - setVideoQuality(to: AVCaptureSession.Preset) + setVideoQuality(to: AVCaptureSession.Preset) throws """, callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } @@ -2522,7 +2654,7 @@ public class AespaCoreSessionRepresentableStub: AespaCoreSessionRepresentable { - public var session: AVCaptureSession { + public var avCaptureSession: AVCaptureSession { get { return DefaultValueRegistry.defaultValue(for: (AVCaptureSession).self) } @@ -2648,7 +2780,7 @@ public class AespaCoreSessionRepresentableStub: AespaCoreSessionRepresentable { - public func setCameraPosition(to position: AVCaptureDevice.Position) throws { + public func setCameraPosition(to position: AVCaptureDevice.Position, device deviceType: AVCaptureDevice.DeviceType?) throws { return DefaultValueRegistry.defaultValue(for: (Void).self) } @@ -2656,7 +2788,7 @@ public class AespaCoreSessionRepresentableStub: AespaCoreSessionRepresentable { - public func setVideoQuality(to preset: AVCaptureSession.Preset) { + public func setVideoQuality(to preset: AVCaptureSession.Preset) throws { return DefaultValueRegistry.defaultValue(for: (Void).self) } @@ -2667,7 +2799,7 @@ public class AespaCoreSessionRepresentableStub: AespaCoreSessionRepresentable { -// MARK: - Mocks generated from file: ../../Sources/Aespa/Core/AespaCoreSession.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Core/AespaCoreSession.swift at 2023-06-13 08:25:34 +0000 // // AespaCoreSessionManager.swift @@ -3008,7 +3140,21 @@ import UIKit -// MARK: - Mocks generated from file: ../../Sources/Aespa/Processor/AespaProcessing.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Data/VideoFile.swift at 2023-06-13 08:25:34 +0000 + +// +// VideoFile.swift +// +// +// Created by 이영빈 on 2023/06/13 +import Cuckoo +@testable import Aespa + +import AVFoundation +import SwiftUI +import UIKit + +// MARK: - Mocks generated from file: ../../Sources/Aespa/Processor/AespaProcessing.swift at 2023-06-13 08:25:34 +0000 // // AespaProcessing.swift @@ -3262,7 +3408,7 @@ import Photos -// MARK: - Mocks generated from file: ../../Sources/Aespa/Processor/Asset/AssetAdditionProcessor.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Processor/Asset/AssetAdditionProcessor.swift at 2023-06-13 08:25:34 +0000 // // AssetAddingProcessor.swift @@ -3275,7 +3421,7 @@ import Cuckoo import Foundation import Photos -// MARK: - Mocks generated from file: ../../Sources/Aespa/Processor/Record/FinishRecordProcessor.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Processor/Record/FinishRecordProcessor.swift at 2023-06-13 08:25:34 +0000 // // FinishRecordingProcessor.swift @@ -3288,7 +3434,7 @@ import Cuckoo import AVFoundation import Combine -// MARK: - Mocks generated from file: ../../Sources/Aespa/Processor/Record/StartRecordProcessor.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Processor/Record/StartRecordProcessor.swift at 2023-06-13 08:25:34 +0000 // // RecordingStarter.swift @@ -3301,7 +3447,7 @@ import Cuckoo import AVFoundation import Combine -// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/AespaTuning.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/AespaTuning.swift at 2023-06-13 08:25:34 +0000 // // AespaTuning.swift @@ -3759,7 +3905,7 @@ public class AespaSessionTuningStub: AespaSessionTuning { -// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Connection/VideoOrientationTuner.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Connection/VideoOrientationTuner.swift at 2023-06-13 08:25:34 +0000 // // VideoOrientationTuner.swift @@ -3771,7 +3917,7 @@ import Cuckoo import AVFoundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Connection/VideoStabilizationTuner.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Connection/VideoStabilizationTuner.swift at 2023-06-13 08:25:34 +0000 // // VideoStabilizationTuner.swift @@ -3783,7 +3929,7 @@ import Cuckoo import AVFoundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Device/AutoFocusTuner.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Device/AutoFocusTuner.swift at 2023-06-13 08:25:34 +0000 // // AutoFocusTuner.swift @@ -3798,7 +3944,7 @@ import Cuckoo import AVFoundation import Foundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Device/ZoomTuner.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Device/ZoomTuner.swift at 2023-06-13 08:25:34 +0000 // // ZoomTuner.swift @@ -3810,7 +3956,7 @@ import Cuckoo import AVFoundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Session/AudioTuner.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Session/AudioTuner.swift at 2023-06-13 08:25:34 +0000 // // File.swift @@ -3822,7 +3968,7 @@ import Cuckoo import AVFoundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Session/CameraPositionTuner.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Session/CameraPositionTuner.swift at 2023-06-13 08:25:34 +0000 // // CameraPositionTuner.swift @@ -3834,7 +3980,7 @@ import Cuckoo import AVFoundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Session/QualityTuner.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Session/QualityTuner.swift at 2023-06-13 08:25:34 +0000 // // QualityTuner.swift @@ -3846,7 +3992,7 @@ import Cuckoo import AVFoundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Session/SessionLaunchTuner.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Session/SessionLaunchTuner.swift at 2023-06-13 08:25:34 +0000 // // SessionLauncher.swift @@ -3858,7 +4004,7 @@ import Cuckoo import AVFoundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Session/SessionTerminationTuner.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Tuner/Session/SessionTerminationTuner.swift at 2023-06-13 08:25:34 +0000 // // SessionTerminationTuner.swift @@ -3872,7 +4018,7 @@ import Cuckoo import AVFoundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Extension/AVFoundation+Extension.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Extension/AVFoundation+Extension.swift at 2023-06-13 08:25:34 +0000 // // AVFoundation + Extension.swift @@ -3886,7 +4032,7 @@ import Cuckoo import AVFoundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Extension/SwiftUI+Extension.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Extension/SwiftUI+Extension.swift at 2023-06-13 08:25:34 +0000 // // SwiftUI + Extension.swift @@ -3902,7 +4048,7 @@ import AVFoundation import Combine import SwiftUI -// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Extension/UIKit+Extension.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Extension/UIKit+Extension.swift at 2023-06-13 08:25:34 +0000 // // UIKit + Extension.swift @@ -3917,7 +4063,7 @@ import Cuckoo import AVFoundation import UIKit -// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Log/Logger.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Log/Logger.swift at 2023-06-13 08:25:34 +0000 // // LoggingManager.swift @@ -4000,7 +4146,7 @@ import Foundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Video/Album/AlbumImporter.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Video/Album/AlbumImporter.swift at 2023-06-13 08:25:34 +0000 // // VideoAlbumProvider.swift @@ -4016,7 +4162,7 @@ import Foundation import Photos import UIKit -// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Video/Authorization/AuthorizationChecker.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Video/Authorization/AuthorizationChecker.swift at 2023-06-13 08:25:34 +0000 // // AuthorizationManager.swift @@ -4031,7 +4177,7 @@ import Cuckoo import AVFoundation import Foundation -// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Video/File/VideoFileGenerator.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Video/File/VideoFileGenerator.swift at 2023-06-13 08:25:34 +0000 // // File.swift @@ -4046,7 +4192,7 @@ import Cuckoo import AVFoundation import UIKit -// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Video/File/VideoFilePathProvider.swift at 2023-06-11 07:19:03 +0000 +// MARK: - Mocks generated from file: ../../Sources/Aespa/Util/Video/File/VideoFilePathProvider.swift at 2023-06-13 08:25:34 +0000 // // VideoFilePathProvidingService.swift diff --git a/Tests/Aespa-iOS-testTests/Tuner/SessionTunerTests.swift b/Tests/Aespa-iOS-testTests/Tuner/SessionTunerTests.swift index 9d94542..164bbf2 100644 --- a/Tests/Aespa-iOS-testTests/Tuner/SessionTunerTests.swift +++ b/Tests/Aespa-iOS-testTests/Tuner/SessionTunerTests.swift @@ -35,7 +35,7 @@ final class SessionTunerTests: XCTestCase { when(proxy.setVideoQuality(to: any())).thenDoNothing() } - tuner.tune(mockSessionProtocol) + try tuner.tune(mockSessionProtocol) verify(mockSessionProtocol) .setVideoQuality(to: equal(to: AVCaptureSession.Preset.cif352x288)) .with(returnType: Void.self) @@ -46,12 +46,12 @@ final class SessionTunerTests: XCTestCase { let tuner = CameraPositionTuner(position: position) stub(mockSessionProtocol) { proxy in - when(proxy.setCameraPosition(to: any())).thenDoNothing() + when(proxy.setCameraPosition(to: any(), device: any())).thenDoNothing() } try tuner.tune(mockSessionProtocol) verify(mockSessionProtocol) - .setCameraPosition(to: equal(to: AVCaptureDevice.Position.front)) + .setCameraPosition(to: equal(to: AVCaptureDevice.Position.front), device: any()) .with(returnType: Void.self) }