Skip to content

Commit

Permalink
[1.0.3] Added unit tests for DelegatesManager and PermissionsManager …
Browse files Browse the repository at this point in the history
…and did some small improvements
  • Loading branch information
andreilob committed May 29, 2023
1 parent 320d396 commit fe7c7e5
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 23 deletions.
33 changes: 13 additions & 20 deletions Sources/CameraKage/CameraKage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ public class CameraKage: UIView {
private var sessionComposer: SessionComposable = SessionComposer()
private let sessionQueue = DispatchQueue(label: "LA.cameraKage.sessionQueue")
private let permissionManager: PermissionsManagerProtocol = PermissionsManager()
private let delegatesManager: DelegatesManagerProtocol = DelegatesManager()
private var cameraComponent: CameraComponent!
private let delegates: NSHashTable<AnyObject> = NSHashTable.weakObjects()

/// Determines if the `AVCaptureSession` of `CameraKage` is running.
public var isSessionRunning: Bool { sessionComposer.isSessionRunning }
Expand All @@ -21,7 +21,7 @@ public class CameraKage: UIView {
- parameter delegate: The object that will receive the notifications.
*/
public func registerDelegate(_ delegate: CameraKageDelegate) {
delegates.add(delegate as AnyObject)
delegatesManager.registerDelegate(delegate)
}

/**
Expand All @@ -30,7 +30,7 @@ public class CameraKage: UIView {
- parameter delegate: The object to be removed.
*/
public func unregisterDelegate(_ delegate: CameraKageDelegate) {
delegates.remove(delegate as AnyObject)
delegatesManager.unregisterDelegate(delegate)
}

/**
Expand Down Expand Up @@ -241,22 +241,22 @@ public class CameraKage: UIView {
private func setupSessionDelegate() {
sessionComposer.onSessionStart = { [weak self] in
guard let self else { return }
invokeDelegates { $0.cameraSessionDidStart(self) }
delegatesManager.invokeDelegates { $0.cameraSessionDidStart(self) }
}

sessionComposer.onSessionStop = { [weak self] in
guard let self else { return }
invokeDelegates { $0.cameraSessionDidStop(self) }
delegatesManager.invokeDelegates { $0.cameraSessionDidStop(self) }
}

sessionComposer.onSessionInterruption = { [weak self] reason in
guard let self else { return }
invokeDelegates { $0.camera(self, sessionWasInterrupted: reason) }
delegatesManager.invokeDelegates { $0.camera(self, sessionWasInterrupted: reason) }
}

sessionComposer.onSessionInterruptionEnd = { [weak self] in
guard let self else { return }
invokeDelegates { $0.cameraSessionInterruptionEnded(self) }
delegatesManager.invokeDelegates { $0.cameraSessionInterruptionEnded(self) }
}

sessionComposer.onSessionReceiveRuntimeError = { [weak self] isRestartable, avError in
Expand All @@ -268,38 +268,31 @@ public class CameraKage: UIView {
}
}
let sessionError = CameraError.CameraSessionErrorReason.runtimeError(avError)
invokeDelegates { $0.camera(self, didEncounterError: .cameraSessionError(reason: sessionError))}
delegatesManager.invokeDelegates { $0.camera(self, didEncounterError: .cameraSessionError(reason: sessionError))}
}

sessionComposer.onDeviceSubjectAreaChange = { [weak self] in
guard let self else { return }
invokeDelegates { $0.cameraDeviceDidChangeSubjectArea(self) }
}
}

private func invokeDelegates(_ execute: (CameraKageDelegate) -> Void) {
delegates.allObjects.forEach { delegate in
guard let delegate = delegate as? CameraKageDelegate else { return }
execute(delegate)
delegatesManager.invokeDelegates { $0.cameraDeviceDidChangeSubjectArea(self) }
}
}
}

// MARK: - CameraComponentDelegate
extension CameraKage: CameraComponentDelegate {
func cameraComponent(_ cameraComponent: CameraComponent, didCapturePhoto photo: Data) {
invokeDelegates { $0.camera(self, didOutputPhotoWithData: photo)}
delegatesManager.invokeDelegates { $0.camera(self, didOutputPhotoWithData: photo)}
}

func cameraComponent(_ cameraComponent: CameraComponent, didStartRecordingVideo atFileURL: URL) {
invokeDelegates { $0.camera(self, didStartRecordingVideoAtFileURL: atFileURL)}
delegatesManager.invokeDelegates { $0.camera(self, didStartRecordingVideoAtFileURL: atFileURL)}
}

func cameraComponent(_ cameraComponent: CameraComponent, didRecordVideo videoURL: URL) {
invokeDelegates { $0.camera(self, didOutputVideoAtFileURL: videoURL)}
delegatesManager.invokeDelegates { $0.camera(self, didOutputVideoAtFileURL: videoURL)}
}

func cameraComponent(_ cameraComponent: CameraComponent, didFail withError: CameraError) {
invokeDelegates { $0.camera(self, didEncounterError: withError) }
delegatesManager.invokeDelegates { $0.camera(self, didEncounterError: withError) }
}
}
35 changes: 35 additions & 0 deletions Sources/CameraKage/General/DelegatesManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// DelegatesManager.swift
//
//
// Created by Lobont Andrei on 29.05.2023.
//

import Foundation

protocol DelegatesManagerProtocol {
var delegates: NSHashTable<AnyObject> { get }

func registerDelegate(_ delegate: CameraKageDelegate)
func unregisterDelegate(_ delegate: CameraKageDelegate)
func invokeDelegates(_ execute: (CameraKageDelegate) -> Void)
}

final class DelegatesManager: DelegatesManagerProtocol {
let delegates: NSHashTable<AnyObject> = NSHashTable.weakObjects()

func registerDelegate(_ delegate: CameraKageDelegate) {
delegates.add(delegate as AnyObject)
}

func unregisterDelegate(_ delegate: CameraKageDelegate) {
delegates.remove(delegate as AnyObject)
}

func invokeDelegates(_ execute: (CameraKageDelegate) -> Void) {
delegates.allObjects.forEach { delegate in
guard let delegate = delegate as? CameraKageDelegate else { return }
execute(delegate)
}
}
}
1 change: 1 addition & 0 deletions Sources/CameraKage/General/Session/SessionComposable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ protocol SessionComposable {
var isSessionRunning: Bool { get }
var outputs: [AVCaptureOutput] { get }
var inputs: [AVCaptureInput] { get }
var connections: [AVCaptureConnection] { get }
var onSessionReceiveRuntimeError: ((Bool, AVError) -> Void)? { get set }
var onSessionStart: (() -> Void)? { get set }
var onSessionStop: (() -> Void)? { get set }
Expand Down
6 changes: 4 additions & 2 deletions Sources/CameraKage/General/Session/SessionComposer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ final class SessionComposer: SessionComposable {
var isSessionRunning: Bool { session.isRunning }
var outputs: [AVCaptureOutput] { session.outputs }
var inputs: [AVCaptureInput] { session.inputs }
var connections: [AVCaptureConnection] { session.connections }
var onSessionReceiveRuntimeError: ((Bool, AVError) -> Void)?
var onSessionStart: (() -> Void)?
var onSessionStop: (() -> Void)?
Expand Down Expand Up @@ -67,8 +68,9 @@ final class SessionComposer: SessionComposable {
}

func cleanupSession() {
session.outputs.forEach { self.session.removeOutput($0) }
session.inputs.forEach { self.session.removeInput($0) }
session.outputs.forEach { session.removeOutput($0) }
session.inputs.forEach { session.removeInput($0) }
session.connections.forEach { session.removeConnection($0) }
}

func startSession() {
Expand Down
13 changes: 13 additions & 0 deletions Tests/CameraKageTests/CameraKageDelegateMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// File.swift
//
//
// Created by Lobont Andrei on 29.05.2023.
//

import Foundation
@testable import CameraKage

final class CameraKageDelegateMock: CameraKageDelegate {
var invoked = false
}
80 changes: 79 additions & 1 deletion Tests/CameraKageTests/CameraKageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,84 @@ import XCTest
@testable import CameraKage

final class CameraKageTests: XCTestCase {
func testExample() throws {
private var delegatesManager: DelegatesManagerProtocol!
private var permissionsManagerMock: PermissionsManagerProtocol!

override func setUp() {
super.setUp()
delegatesManager = DelegatesManagerMock()
permissionsManagerMock = PermissionManagerMock()
}

override func tearDown() {
super.tearDown()
delegatesManager = nil
permissionsManagerMock = nil
}

func testDelegateRegistration() {
XCTAssertEqual(delegatesManager.delegates.allObjects.count, 0)

let firstDelegate = CameraKageDelegateMock()
let secondDelegate = CameraKageDelegateMock()
delegatesManager.registerDelegate(firstDelegate)
delegatesManager.registerDelegate(secondDelegate)

XCTAssertEqual(delegatesManager.delegates.allObjects.count, 2)

delegatesManager.unregisterDelegate(firstDelegate)
XCTAssertEqual(delegatesManager.delegates.allObjects.count, 1)
}

func testDelegateInvocation() {
let firstDelegate = CameraKageDelegateMock()
let secondDelegate = CameraKageDelegateMock()
delegatesManager.registerDelegate(firstDelegate)
XCTAssertFalse(firstDelegate.invoked)
XCTAssertFalse(secondDelegate.invoked)
delegatesManager.invokeDelegates { delegate in
let delegate = delegate as? CameraKageDelegateMock
XCTAssertEqual(delegate?.invoked, true)
}
delegatesManager.registerDelegate(secondDelegate)
XCTAssertTrue(firstDelegate.invoked)
XCTAssertFalse(secondDelegate.invoked)
}

func testGetCameraPermissionStatusWithCallback() {
XCTAssertEqual(permissionsManagerMock.getAuthorizationStatus(for: .video), .denied)
XCTAssertEqual(permissionsManagerMock.getAuthorizationStatus(for: .audio), .denied)
permissionsManagerMock.requestAccess(for: .video) { granted in
XCTAssertTrue(granted)
XCTAssertEqual(self.permissionsManagerMock.getAuthorizationStatus(for: .video), .authorized)
XCTAssertEqual(self.permissionsManagerMock.getAuthorizationStatus(for: .audio), .denied)
}
}

func testGetCameraPermissionStatus() async {
XCTAssertEqual(permissionsManagerMock.getAuthorizationStatus(for: .video), .denied)
XCTAssertEqual(permissionsManagerMock.getAuthorizationStatus(for: .audio), .denied)
let granted = await permissionsManagerMock.requestAccess(for: .video)
XCTAssertTrue(granted)
XCTAssertEqual(permissionsManagerMock.getAuthorizationStatus(for: .video), .authorized)
XCTAssertEqual(permissionsManagerMock.getAuthorizationStatus(for: .audio), .denied)
}

func testGetMicrophonePermissionStatusWithCallback() {
XCTAssertEqual(permissionsManagerMock.getAuthorizationStatus(for: .video), .denied)
XCTAssertEqual(permissionsManagerMock.getAuthorizationStatus(for: .audio), .denied)
permissionsManagerMock.requestAccess(for: .audio) { granted in
XCTAssertTrue(granted)
XCTAssertEqual(self.permissionsManagerMock.getAuthorizationStatus(for: .video), .denied)
XCTAssertEqual(self.permissionsManagerMock.getAuthorizationStatus(for: .audio), .authorized)
}
}

func testGetMicrophonePermissionStatus() async {
XCTAssertEqual(permissionsManagerMock.getAuthorizationStatus(for: .audio), .denied)
let granted = await permissionsManagerMock.requestAccess(for: .audio)
XCTAssertTrue(granted)
XCTAssertEqual(permissionsManagerMock.getAuthorizationStatus(for: .video), .denied)
XCTAssertEqual(permissionsManagerMock.getAuthorizationStatus(for: .audio), .authorized)
}
}
29 changes: 29 additions & 0 deletions Tests/CameraKageTests/DelegatesManagerMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// DelegatesManagerMock.swift
//
//
// Created by Lobont Andrei on 29.05.2023.
//

import Foundation
@testable import CameraKage

final class DelegatesManagerMock: DelegatesManagerProtocol {
var delegates: NSHashTable<AnyObject> = NSHashTable.weakObjects()

func registerDelegate(_ delegate: CameraKageDelegate) {
delegates.add(delegate as AnyObject)
}

func unregisterDelegate(_ delegate: CameraKageDelegate) {
delegates.remove(delegate as AnyObject)
}

func invokeDelegates(_ execute: (CameraKageDelegate) -> Void) {
delegates.allObjects.forEach { delegate in
guard let delegate = delegate as? CameraKageDelegateMock else { return }
delegate.invoked = true
execute(delegate)
}
}
}
48 changes: 48 additions & 0 deletions Tests/CameraKageTests/PermissionManagerMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// File.swift
//
//
// Created by Lobont Andrei on 29.05.2023.
//

import AVFoundation
@testable import CameraKage

class PermissionManagerMock: PermissionsManagerProtocol {
private var authorizedVideo = false
private var authorizedAudio = false

func getAuthorizationStatus(for media: AVMediaType) -> PermissionStatus {
switch media {
case .audio: return authorizedAudio ? .authorized : .denied
case .video: return authorizedVideo ? .authorized : .denied
default: return .notDetermined // not using other media type yet
}
}

func requestAccess(for media: AVMediaType) async -> Bool {
switch media {
case .audio:
authorizedAudio = true
return authorizedAudio
case .video:
authorizedVideo = true
return authorizedVideo
default:
return false // not using other media type yet
}
}

func requestAccess(for media: AVMediaType, completion: @escaping ((Bool) -> Void)) {
switch media {
case .audio:
authorizedAudio = true
completion(authorizedAudio)
case .video:
authorizedVideo = true
completion(authorizedVideo)
default:
break // not using other media type yet
}
}
}

0 comments on commit fe7c7e5

Please sign in to comment.