twonly-app-dependencies/camera_avfoundation/darwin/Tests/CameraOrientationTests.swift
2026-10-07 22:18:23 +02:00

168 lines
6.3 KiB
Swift

// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import AVFoundation
import Flutter
import XCTest
@testable import camera_avfoundation
private final class MockUIDevice: UIDevice {
var mockOrientation: UIDeviceOrientation = .unknown
override var orientation: UIDeviceOrientation {
return mockOrientation
}
}
final class CameraOrientationTests: XCTestCase {
private func createCameraPlugin() -> (
cameraPlugin: CameraPlugin,
mockCamera: MockCamera,
mockEventAPI: MockGlobalEventApi,
mockDevice: MockCaptureDevice,
mockDeviceDiscoverer: MockCameraDeviceDiscoverer,
captureSessionQueue: DispatchQueue
) {
let mockDevice = MockCaptureDevice()
let mockCamera = MockCamera()
let mockEventAPI = MockGlobalEventApi()
let mockDeviceDiscoverer = MockCameraDeviceDiscoverer()
let captureSessionQueue = DispatchQueue(label: "io.flutter.camera.captureSessionQueue")
let cameraPlugin = CameraPlugin(
registry: MockFlutterTextureRegistry(),
messenger: MockFlutterBinaryMessenger(),
globalAPI: mockEventAPI,
deviceDiscoverer: mockDeviceDiscoverer,
permissionManager: MockCameraPermissionManager(),
deviceFactory: { _ in mockDevice },
captureSessionFactory: { MockCaptureSession() },
captureDeviceInputFactory: MockCaptureDeviceInputFactory(),
captureSessionQueue: captureSessionQueue
)
cameraPlugin.camera = mockCamera
return (
cameraPlugin,
mockCamera,
mockEventAPI,
mockDevice,
mockDeviceDiscoverer,
captureSessionQueue
)
}
private func sendOrientation(
_ orientation: UIDeviceOrientation,
to cameraPlugin: CameraPlugin,
captureSessionQueue: DispatchQueue
) {
cameraPlugin.orientationChanged(createMockNotification(for: orientation))
waitForQueueRoundTrip(with: captureSessionQueue)
}
private func createMockNotification(for deviceOrientation: UIDeviceOrientation) -> Notification {
let mockDevice = MockUIDevice()
mockDevice.mockOrientation = deviceOrientation
return Notification(name: Notification.Name("orientation_test"), object: mockDevice)
}
func testOrientationNotifications() {
let (cameraPlugin, _, mockEventAPI, _, _, captureSessionQueue) = createCameraPlugin()
sendOrientation(.portraitUpsideDown, to: cameraPlugin, captureSessionQueue: captureSessionQueue)
XCTAssertEqual(mockEventAPI.lastOrientation, .portraitDown)
sendOrientation(.portrait, to: cameraPlugin, captureSessionQueue: captureSessionQueue)
XCTAssertEqual(mockEventAPI.lastOrientation, .portraitUp)
sendOrientation(.landscapeLeft, to: cameraPlugin, captureSessionQueue: captureSessionQueue)
XCTAssertEqual(mockEventAPI.lastOrientation, .landscapeLeft)
sendOrientation(.landscapeRight, to: cameraPlugin, captureSessionQueue: captureSessionQueue)
XCTAssertEqual(mockEventAPI.lastOrientation, .landscapeRight)
}
func testOrientationNotificationsNotCalledForFaceUp() {
let (cameraPlugin, _, mockEventAPI, _, _, captureSessionQueue) = createCameraPlugin()
sendOrientation(.faceUp, to: cameraPlugin, captureSessionQueue: captureSessionQueue)
XCTAssertFalse(mockEventAPI.deviceOrientationChangedCalled)
}
func testOrientationNotificationsNotCalledForFaceDown() {
let (cameraPlugin, _, mockEventAPI, _, _, captureSessionQueue) = createCameraPlugin()
sendOrientation(.faceDown, to: cameraPlugin, captureSessionQueue: captureSessionQueue)
XCTAssertFalse(mockEventAPI.deviceOrientationChangedCalled)
}
func testOrientationUpdateMustBeOnCaptureSessionQueue() {
let queueExpectation = expectation(
description: "Orientation update must happen on the capture session queue")
let (cameraPlugin, mockCamera, _, _, _, captureSessionQueue) = createCameraPlugin()
let captureSessionQueueSpecific = DispatchSpecificKey<Void>()
captureSessionQueue.setSpecific(
key: captureSessionQueueSpecific,
value: ())
mockCamera.setDeviceOrientationStub = { orientation in
if DispatchQueue.getSpecific(key: captureSessionQueueSpecific) != nil {
queueExpectation.fulfill()
}
}
cameraPlugin.orientationChanged(createMockNotification(for: .landscapeLeft))
waitForExpectations(timeout: 30, handler: nil)
}
func testOrientationChangedNoRetainCycle() {
let (_, mockCamera, mockEventAPI, mockDevice, mockDeviceDiscoverer, _) = createCameraPlugin()
let captureSessionQueue = DispatchQueue(label: "capture_session_queue")
weak var weakPlugin: CameraPlugin?
weak var weakDevice = mockDevice
autoreleasepool {
let cameraPlugin = CameraPlugin(
registry: MockFlutterTextureRegistry(),
messenger: MockFlutterBinaryMessenger(),
globalAPI: mockEventAPI,
deviceDiscoverer: mockDeviceDiscoverer,
permissionManager: MockCameraPermissionManager(),
deviceFactory: { _ in weakDevice! },
captureSessionFactory: { MockCaptureSession() },
captureDeviceInputFactory: MockCaptureDeviceInputFactory(),
captureSessionQueue: captureSessionQueue
)
weakPlugin = cameraPlugin
cameraPlugin.camera = mockCamera
cameraPlugin.orientationChanged(createMockNotification(for: .landscapeLeft))
}
// Sanity check.
let cameraDeallocatedExpectation = self.expectation(
description: "Camera must have been deallocated.")
captureSessionQueue.async {
XCTAssertNil(weakPlugin)
cameraDeallocatedExpectation.fulfill()
}
// Awaiting expectation is needed. The test is flaky when checking for nil right away.
waitForExpectations(timeout: 1, handler: nil)
var setDeviceOrientationCalled = false
mockCamera.setDeviceOrientationStub = { orientation in
if orientation == .landscapeLeft {
setDeviceOrientationCalled = true
}
}
weak var weakEventAPI = mockEventAPI
// Must check in captureSessionQueue since orientationChanged dispatches to this queue.
let expectation = self.expectation(description: "Dispatched to capture session queue")
captureSessionQueue.async {
XCTAssertFalse(setDeviceOrientationCalled)
XCTAssertFalse(weakEventAPI?.deviceOrientationChangedCalled ?? false)
expectation.fulfill()
}
waitForExpectations(timeout: 30, handler: nil)
}
}