From 5de25874862fecc837a17f65461a8f3acf6aaec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Tue, 11 Feb 2025 16:03:43 +0100 Subject: [PATCH 01/40] Migrate CameraCaptureSessionQueueRaceConditionTests to Swift --- .../ios/Runner.xcodeproj/project.pbxproj | 22 ++++-- ...eraCaptureSessionQueueRaceConditionTests.m | 72 ------------------- ...aptureSessionQueueRaceConditionTests.swift | 57 +++++++++++++++ .../RunnerTests/RunnerTests-Bridging-Header.h | 23 ++++++ 4 files changed, 98 insertions(+), 76 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.m create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index 75716ac04cf..cc7c1d6cafc 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -29,10 +29,11 @@ 7FA99E592D22C75300582559 /* CameraExposureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FA99E582D22C75300582559 /* CameraExposureTests.m */; }; 7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FCEDD342D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m */; }; 7FCEDD362D43C2B900EA1CA8 /* MockCaptureDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FCEDD322D43C2B900EA1CA8 /* MockCaptureDevice.m */; }; - 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */; }; 7FD582122D579650003B1200 /* MockWritableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582112D579650003B1200 /* MockWritableData.m */; }; 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */; }; + 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -105,14 +106,16 @@ 7FCEDD322D43C2B900EA1CA8 /* MockCaptureDevice.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureDevice.m; sourceTree = ""; }; 7FCEDD332D43C2B900EA1CA8 /* MockDeviceOrientationProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockDeviceOrientationProvider.h; sourceTree = ""; }; 7FCEDD342D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockDeviceOrientationProvider.m; sourceTree = ""; }; - 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureDeviceFormat.m; sourceTree = ""; }; - 7FD582362D57D989003B1200 /* MockCaptureDeviceFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureDeviceFormat.h; sourceTree = ""; }; 7FD582112D579650003B1200 /* MockWritableData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockWritableData.m; sourceTree = ""; }; 7FD582132D57965A003B1200 /* MockWritableData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockWritableData.h; sourceTree = ""; }; 7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCapturePhotoOutput.m; sourceTree = ""; }; 7FD582212D579ED9003B1200 /* MockCapturePhotoOutput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCapturePhotoOutput.h; sourceTree = ""; }; + 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureDeviceFormat.m; sourceTree = ""; }; + 7FD582362D57D989003B1200 /* MockCaptureDeviceFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureDeviceFormat.h; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; + 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCaptureSessionQueueRaceConditionTests.swift; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -184,6 +187,8 @@ 788A065927B0E02900533D74 /* StreamingTest.m */, 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */, CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */, + 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */, + 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */, ); path = RunnerTests; sourceTree = ""; @@ -354,6 +359,7 @@ TargetAttributes = { 03BB76672665316900CE5A93 = { CreatedOnToolsVersion = 12.5; + LastSwiftMigration = 1540; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; @@ -522,6 +528,7 @@ E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */, E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */, 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */, + 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */, 7F29EB222D269ED500740257 /* MockEventChannel.m in Sources */, 7F8FD22F2D4D0B88001AF2C1 /* MockFlutterBinaryMessenger.m in Sources */, E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */, @@ -590,6 +597,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; @@ -607,6 +615,9 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "dev.flutter.plugins.cameraExample.camera-exampleTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "RunnerTests/RunnerTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; @@ -619,6 +630,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; @@ -635,6 +647,8 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "dev.flutter.plugins.cameraExample.camera-exampleTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "RunnerTests/RunnerTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.m deleted file mode 100644 index b651fec7864..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.m +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import XCTest; - -#import "MockCameraDeviceDiscoverer.h" -#import "MockCaptureDevice.h" -#import "MockCaptureSession.h" -#import "MockFlutterBinaryMessenger.h" -#import "MockFlutterTextureRegistry.h" -#import "MockGlobalEventApi.h" - -@interface CameraCaptureSessionQueueRaceConditionTests : XCTestCase -@end - -@implementation CameraCaptureSessionQueueRaceConditionTests - -- (CameraPlugin *)createCameraPlugin { - MockCaptureDevice *captureDevice = [[MockCaptureDevice alloc] init]; - - return [[CameraPlugin alloc] initWithRegistry:[[MockFlutterTextureRegistry alloc] init] - messenger:[[MockFlutterBinaryMessenger alloc] init] - globalAPI:[[MockGlobalEventApi alloc] init] - deviceDiscoverer:[[MockCameraDeviceDiscoverer alloc] init] - deviceFactory:^NSObject *(NSString *name) { - return captureDevice; - } - captureSessionFactory:^NSObject * { - return [[MockCaptureSession alloc] init]; - } - captureDeviceInputFactory:[[MockCaptureDeviceInputFactory alloc] init]]; -} - -- (void)testFixForCaptureSessionQueueNullPointerCrashDueToRaceCondition { - CameraPlugin *cameraPlugin = [self createCameraPlugin]; - - XCTestExpectation *disposeExpectation = - [self expectationWithDescription:@"dispose's result block must be called"]; - XCTestExpectation *createExpectation = - [self expectationWithDescription:@"create's result block must be called"]; - // Mimic a dispose call followed by a create call, which can be triggered by slightly dragging the - // home bar, causing the app to be inactive, and immediately regain active. - [cameraPlugin disposeCamera:0 - completion:^(FlutterError *_Nullable error) { - [disposeExpectation fulfill]; - }]; - [cameraPlugin createCameraOnSessionQueueWithName:@"acamera" - settings:[FCPPlatformMediaSettings - makeWithResolutionPreset: - FCPPlatformResolutionPresetMedium - framesPerSecond:nil - videoBitrate:nil - audioBitrate:nil - enableAudio:YES] - completion:^(NSNumber *_Nullable result, - FlutterError *_Nullable error) { - [createExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:30 handler:nil]; - // `captureSessionQueue` must not be nil after `create` call. Otherwise a nil - // `captureSessionQueue` passed into `AVCaptureVideoDataOutput::setSampleBufferDelegate:queue:` - // API will cause a crash. - XCTAssertNotNil(cameraPlugin.captureSessionQueue, - @"captureSessionQueue must not be nil after create method. "); -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift new file mode 100644 index 00000000000..6da49ca1257 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import XCTest + +@testable import camera_avfoundation + +final class CameraCaptureSessionQueueRaceConditionTests: XCTestCase { + func createCameraPlugin() -> CameraPlugin { + let captureDevice = MockCaptureDevice() + + return CameraPlugin( + registry: MockFlutterTextureRegistry(), + messenger: MockFlutterBinaryMessenger(), + globalAPI: MockGlobalEventApi(), + deviceDiscoverer: MockCameraDeviceDiscoverer(), + deviceFactory: { _ in captureDevice }, + captureSessionFactory: { MockCaptureSession() }, + captureDeviceInputFactory: MockCaptureDeviceInputFactory() + ) + } + + func testFixForCaptureSessionQueueNullPointerCrashDueToRaceCondition() { + let cameraPlugin = createCameraPlugin() + + let disposeExpectation = expectation(description: "dispose's result block must be called") + let createExpectation = expectation(description: "create's result block must be called") + + // Mimic a dispose call followed by a create call, which can be triggered by slightly dragging the + // home bar, causing the app to be inactive, and immediately regain active. + cameraPlugin.disposeCamera(0) { error in + disposeExpectation.fulfill() + } + + cameraPlugin.createCameraOnSessionQueue( + withName: "acamera", + settings: FCPPlatformMediaSettings.make( + with: .medium, + framesPerSecond: nil, + videoBitrate: nil, + audioBitrate: nil, + enableAudio: true + ) + ) { result, error in + createExpectation.fulfill() + } + + waitForExpectations(timeout: 30, handler: nil) + + // `captureSessionQueue` must not be nil after `create` call. Otherwise a nil + // `captureSessionQueue` passed into `AVCaptureVideoDataOutput::setSampleBufferDelegate:queue:` + // API will cause a crash. + XCTAssertNotNil( + cameraPlugin.captureSessionQueue, "captureSessionQueue must not be nil after create method.") + } +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h new file mode 100644 index 00000000000..581002f9cf6 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -0,0 +1,23 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Sources. +#import "camera_avfoundation/CameraPlugin.h" +#import "camera_avfoundation/CameraPlugin_Test.h" +#import "camera_avfoundation/FLTCam.h" +#import "camera_avfoundation/FLTCamConfiguration.h" +#import "camera_avfoundation/FLTThreadSafeEventChannel.h" + +// Mocks, protocols. +#import "MockCameraDeviceDiscoverer.h" +#import "MockCaptureDevice.h" +#import "MockCaptureSession.h" +#import "MockDeviceOrientationProvider.h" +#import "MockEventChannel.h" +#import "MockFlutterBinaryMessenger.h" +#import "MockFlutterTextureRegistry.h" +#import "MockGlobalEventApi.h" + +// Utils. +#import "CameraTestUtils.h" From a7b3c029e6d7c0843c8d514d4a6919895c711e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Tue, 11 Feb 2025 16:29:24 +0100 Subject: [PATCH 02/40] Migrate CameraMethodChannelTests to Swift --- .../ios/Runner.xcodeproj/project.pbxproj | 14 +-- .../RunnerTests/CameraMethodChannelTests.m | 100 ------------------ .../CameraMethodChannelTests.swift | 82 ++++++++++++++ 3 files changed, 87 insertions(+), 109 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.swift diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index cc7c1d6cafc..b414390962a 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,11 +3,10 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ - 033B94BE269C40A200B4DF97 /* CameraMethodChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */; }; 03BB766B2665316900CE5A93 /* CameraFocusTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BB766A2665316900CE5A93 /* CameraFocusTests.m */; }; 1000364CB781922C6D6AAA4A /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DDC4CE84A8B378AE4A8CD9C /* libPods-RunnerTests.a */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; @@ -34,13 +33,13 @@ 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; + 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */; }; E01EE4A82799F3A5008C1950 /* QueueUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */; }; - E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */; }; E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */; }; E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */; }; E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */; }; @@ -74,7 +73,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraMethodChannelTests.m; sourceTree = ""; }; 03BB76682665316900CE5A93 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 03BB766A2665316900CE5A93 /* CameraFocusTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraFocusTests.m; sourceTree = ""; }; 03BB766C2665316900CE5A93 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -116,6 +114,7 @@ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCaptureSessionQueueRaceConditionTests.swift; sourceTree = ""; }; + 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraMethodChannelTests.swift; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -127,7 +126,6 @@ B61D98BBC8FB276D1C4A7BB2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = CameraSessionPresetsTests.m; sourceTree = ""; }; E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QueueUtilsTests.m; sourceTree = ""; }; - E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraCaptureSessionQueueRaceConditionTests.m; sourceTree = ""; }; E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTSavePhotoDelegateTests.m; sourceTree = ""; }; E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCamPhotoCaptureTests.m; sourceTree = ""; }; E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTCamSampleBufferTests.m; sourceTree = ""; }; @@ -172,7 +170,6 @@ 7FA99E582D22C75300582559 /* CameraExposureTests.m */, 03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */, 03BB766C2665316900CE5A93 /* Info.plist */, - 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */, E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */, E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */, E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */, @@ -182,13 +179,13 @@ E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */, E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */, E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */, - E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */, E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */, 788A065927B0E02900533D74 /* StreamingTest.m */, 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */, CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */, 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */, 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */, + 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -517,7 +514,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 033B94BE269C40A200B4DF97 /* CameraMethodChannelTests.m in Sources */, E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */, 7F56D0382D1EDDCE005676A5 /* CameraPermissionTests.m in Sources */, E0F95E3D27A32AB900699390 /* CameraPropertiesTests.m in Sources */, @@ -537,7 +533,7 @@ 334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */, 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */, CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTests.m in Sources */, - E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */, + 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */, 7F29EB292D26A59000740257 /* MockCameraDeviceDiscoverer.m in Sources */, 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */, E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m deleted file mode 100644 index 1f084f5ba75..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import XCTest; -@import AVFoundation; - -#import "MockCameraDeviceDiscoverer.h" -#import "MockCaptureDevice.h" -#import "MockCaptureSession.h" -#import "MockFlutterBinaryMessenger.h" -#import "MockFlutterTextureRegistry.h" -#import "MockGlobalEventApi.h" - -@interface CameraMethodChannelTests : XCTestCase -@end - -@implementation CameraMethodChannelTests - -- (CameraPlugin *)createCameraPluginWithSession:(MockCaptureSession *)mockSession { - return [[CameraPlugin alloc] initWithRegistry:[[MockFlutterTextureRegistry alloc] init] - messenger:[[MockFlutterBinaryMessenger alloc] init] - globalAPI:[[MockGlobalEventApi alloc] init] - deviceDiscoverer:[[MockCameraDeviceDiscoverer alloc] init] - deviceFactory:^NSObject *(NSString *name) { - return [[MockCaptureDevice alloc] init]; - } - captureSessionFactory:^NSObject *_Nonnull { - return mockSession; - } - captureDeviceInputFactory:[[MockCaptureDeviceInputFactory alloc] init]]; -} - -- (void)testCreate_ShouldCallResultOnMainThread { - MockCaptureSession *avCaptureSessionMock = [[MockCaptureSession alloc] init]; - avCaptureSessionMock.canSetSessionPreset = YES; - - CameraPlugin *camera = [self createCameraPluginWithSession:avCaptureSessionMock]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"]; - - // Set up method call - __block NSNumber *resultValue; - [camera createCameraOnSessionQueueWithName:@"acamera" - settings:[FCPPlatformMediaSettings - makeWithResolutionPreset: - FCPPlatformResolutionPresetMedium - framesPerSecond:nil - videoBitrate:nil - audioBitrate:nil - enableAudio:YES] - completion:^(NSNumber *_Nullable result, - FlutterError *_Nullable error) { - resultValue = result; - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:30 handler:nil]; - - // Verify the result - XCTAssertNotNil(resultValue); -} - -- (void)testDisposeShouldDeallocCamera { - MockCaptureSession *avCaptureSessionMock = [[MockCaptureSession alloc] init]; - avCaptureSessionMock.canSetSessionPreset = YES; - - CameraPlugin *camera = [self createCameraPluginWithSession:avCaptureSessionMock]; - - XCTestExpectation *createExpectation = - [self expectationWithDescription:@"create's result block must be called"]; - [camera createCameraOnSessionQueueWithName:@"acamera" - settings:[FCPPlatformMediaSettings - makeWithResolutionPreset: - FCPPlatformResolutionPresetMedium - framesPerSecond:nil - videoBitrate:nil - audioBitrate:nil - enableAudio:YES] - completion:^(NSNumber *_Nullable result, - FlutterError *_Nullable error) { - [createExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:30 handler:nil]; - XCTAssertNotNil(camera.camera); - - XCTestExpectation *disposeExpectation = - [self expectationWithDescription:@"dispose's result block must be called"]; - [camera disposeCamera:0 - completion:^(FlutterError *_Nullable error) { - [disposeExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:30 handler:nil]; - XCTAssertNil(camera.camera, @"camera should be deallocated after dispose"); -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.swift new file mode 100644 index 00000000000..eda19a30ddb --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.swift @@ -0,0 +1,82 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import AVFoundation +import XCTest + +@testable import camera_avfoundation + +final class CameraMethodChannelTests: XCTestCase { + func createCameraPlugin(with session: MockCaptureSession) -> CameraPlugin { + return CameraPlugin( + registry: MockFlutterTextureRegistry(), + messenger: MockFlutterBinaryMessenger(), + globalAPI: MockGlobalEventApi(), + deviceDiscoverer: MockCameraDeviceDiscoverer(), + deviceFactory: { _ in MockCaptureDevice() }, + captureSessionFactory: { session }, + captureDeviceInputFactory: MockCaptureDeviceInputFactory() + ) + } + + func testCreate_ShouldCallResultOnMainThread() { + let avCaptureSessionMock = MockCaptureSession() + avCaptureSessionMock.canSetSessionPreset = true + + let camera = createCameraPlugin(with: avCaptureSessionMock) + + let expectation = self.expectation(description: "Result finished") + + var resultValue: NSNumber? + camera.createCameraOnSessionQueue( + withName: "acamera", + settings: FCPPlatformMediaSettings.make( + with: FCPPlatformResolutionPreset.medium, + framesPerSecond: nil, + videoBitrate: nil, + audioBitrate: nil, + enableAudio: true + ) + ) { result, error in + resultValue = result + expectation.fulfill() + } + + waitForExpectations(timeout: 30, handler: nil) + + XCTAssertNotNil(resultValue) + } + + func testDisposeShouldDeallocCamera() { + let avCaptureSessionMock = MockCaptureSession() + avCaptureSessionMock.canSetSessionPreset = true + + let camera = createCameraPlugin(with: avCaptureSessionMock) + + let createExpectation = self.expectation(description: "create's result block must be called") + camera.createCameraOnSessionQueue( + withName: "acamera", + settings: FCPPlatformMediaSettings.make( + with: .medium, + framesPerSecond: nil, + videoBitrate: nil, + audioBitrate: nil, + enableAudio: true + ) + ) { result, error in + createExpectation.fulfill() + } + + waitForExpectations(timeout: 30, handler: nil) + XCTAssertNotNil(camera.camera) + + let disposeExpectation = self.expectation(description: "dispose's result block must be called") + camera.disposeCamera(0) { error in + disposeExpectation.fulfill() + } + + waitForExpectations(timeout: 30, handler: nil) + XCTAssertNil(camera.camera, "camera should be deallocated after dispose") + } +} From c91bbaed0a07a35b7babf7598ab1cfbebc16fd49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Wed, 12 Feb 2025 12:55:00 +0100 Subject: [PATCH 03/40] Migrate CameraOrientationTests to Swift --- .../ios/Runner.xcodeproj/project.pbxproj | 8 +- .../ios/RunnerTests/CameraOrientationTests.m | 203 ------------------ .../RunnerTests/CameraOrientationTests.swift | 169 +++++++++++++++ .../include/camera_avfoundation/FLTCam.h | 3 +- 4 files changed, 175 insertions(+), 208 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.m create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index b414390962a..b30efcec445 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 03BB766B2665316900CE5A93 /* CameraFocusTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BB766A2665316900CE5A93 /* CameraFocusTests.m */; }; 1000364CB781922C6D6AAA4A /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DDC4CE84A8B378AE4A8CD9C /* libPods-RunnerTests.a */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 408D7A792C3C9CD000B71F9A /* OCMock in Frameworks */ = {isa = PBXBuildFile; productRef = 408D7A782C3C9CD000B71F9A /* OCMock */; }; 43ED1537282570DE00EB00DE /* AvailableCamerasTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */; }; @@ -34,6 +33,7 @@ 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; + 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -76,7 +76,6 @@ 03BB76682665316900CE5A93 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 03BB766A2665316900CE5A93 /* CameraFocusTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraFocusTests.m; sourceTree = ""; }; 03BB766C2665316900CE5A93 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraOrientationTests.m; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; @@ -115,6 +114,7 @@ 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCaptureSessionQueueRaceConditionTests.swift; sourceTree = ""; }; 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraMethodChannelTests.swift; sourceTree = ""; }; + 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraOrientationTests.swift; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -168,7 +168,6 @@ 7D5FCCD32AEF9D0200FB7108 /* CameraSettingsTests.m */, 03BB766A2665316900CE5A93 /* CameraFocusTests.m */, 7FA99E582D22C75300582559 /* CameraExposureTests.m */, - 03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */, 03BB766C2665316900CE5A93 /* Info.plist */, E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */, E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */, @@ -186,6 +185,7 @@ 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */, 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */, 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */, + 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -530,13 +530,13 @@ E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */, 43ED1537282570DE00EB00DE /* AvailableCamerasTest.m in Sources */, E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */, - 334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */, 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */, CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTests.m in Sources */, 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */, 7F29EB292D26A59000740257 /* MockCameraDeviceDiscoverer.m in Sources */, 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */, E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, + 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */, 7F29EB412D281C7E00740257 /* MockCaptureSession.m in Sources */, E01EE4A82799F3A5008C1950 /* QueueUtilsTests.m in Sources */, 7FD582122D579650003B1200 /* MockWritableData.m in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.m deleted file mode 100644 index 9f988c2cdfd..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.m +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import XCTest; -@import Flutter; - -#import "MockCameraDeviceDiscoverer.h" -#import "MockCaptureDevice.h" -#import "MockCaptureSession.h" -#import "MockDeviceOrientationProvider.h" -#import "MockFlutterBinaryMessenger.h" -#import "MockFlutterTextureRegistry.h" -#import "MockGlobalEventApi.h" - -@interface MockCamera : FLTCam -@property(nonatomic, copy) void (^setDeviceOrientationStub)(UIDeviceOrientation orientation); -@end - -@implementation MockCamera -- (void)setDeviceOrientation:(UIDeviceOrientation)orientation { - if (self.setDeviceOrientationStub) { - self.setDeviceOrientationStub(orientation); - } -} - -- (void)setCaptureDevice:(NSObject *)device { - self.captureDevice = device; -} - -@end - -@interface MockUIDevice : UIDevice -@property(nonatomic, assign) UIDeviceOrientation mockOrientation; -@end - -@implementation MockUIDevice -- (UIDeviceOrientation)orientation { - return self.mockOrientation; -} - -@end - -#pragma mark - - -@interface CameraOrientationTests : XCTestCase -@property(readonly, nonatomic) MockCamera *camera; -@property(readonly, nonatomic) MockCaptureDevice *mockDevice; -@property(readonly, nonatomic) MockGlobalEventApi *eventAPI; -@property(readonly, nonatomic) CameraPlugin *cameraPlugin; -@property(readonly, nonatomic) MockCameraDeviceDiscoverer *deviceDiscoverer; -@end - -@implementation CameraOrientationTests - -- (void)setUp { - [super setUp]; - MockCaptureDevice *mockDevice = [[MockCaptureDevice alloc] init]; - _camera = [[MockCamera alloc] init]; - _eventAPI = [[MockGlobalEventApi alloc] init]; - _mockDevice = mockDevice; - _deviceDiscoverer = [[MockCameraDeviceDiscoverer alloc] init]; - - _cameraPlugin = [[CameraPlugin alloc] initWithRegistry:[[MockFlutterTextureRegistry alloc] init] - messenger:[[MockFlutterBinaryMessenger alloc] init] - globalAPI:_eventAPI - deviceDiscoverer:_deviceDiscoverer - deviceFactory:^NSObject *(NSString *name) { - return mockDevice; - } - captureSessionFactory:^NSObject *_Nonnull { - return [[MockCaptureSession alloc] init]; - } - captureDeviceInputFactory:[[MockCaptureDeviceInputFactory alloc] init]]; - _cameraPlugin.camera = _camera; -} - -// Ensure that the given queue and then the main queue have both cycled, to wait for any pending -// async events that may have been bounced between them. -- (void)waitForRoundTripWithQueue:(dispatch_queue_t)queue { - XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Queue flush"]; - dispatch_async(queue, ^{ - dispatch_async(dispatch_get_main_queue(), ^{ - [expectation fulfill]; - }); - }); - [self waitForExpectations:@[ expectation ]]; -} - -- (void)sendOrientation:(UIDeviceOrientation)orientation toCamera:(CameraPlugin *)cameraPlugin { - [cameraPlugin orientationChanged:[self createMockNotificationForOrientation:orientation]]; - [self waitForRoundTripWithQueue:cameraPlugin.captureSessionQueue]; -} - -- (void)testOrientationNotifications { - [self sendOrientation:UIDeviceOrientationPortraitUpsideDown toCamera:_cameraPlugin]; - XCTAssertEqual(_eventAPI.lastOrientation, FCPPlatformDeviceOrientationPortraitDown); - [self sendOrientation:UIDeviceOrientationPortrait toCamera:_cameraPlugin]; - XCTAssertEqual(_eventAPI.lastOrientation, FCPPlatformDeviceOrientationPortraitUp); - [self sendOrientation:UIDeviceOrientationLandscapeLeft toCamera:_cameraPlugin]; - XCTAssertEqual(_eventAPI.lastOrientation, FCPPlatformDeviceOrientationLandscapeLeft); - [self sendOrientation:UIDeviceOrientationLandscapeRight toCamera:_cameraPlugin]; - XCTAssertEqual(_eventAPI.lastOrientation, FCPPlatformDeviceOrientationLandscapeRight); -} - -- (void)testOrientationNotificationsNotCalledForFaceUp { - [self sendOrientation:UIDeviceOrientationFaceUp toCamera:_cameraPlugin]; - - XCTAssertFalse(_eventAPI.deviceOrientationChangedCalled); -} - -- (void)testOrientationNotificationsNotCalledForFaceDown { - [self sendOrientation:UIDeviceOrientationFaceDown toCamera:_cameraPlugin]; - - XCTAssertFalse(_eventAPI.deviceOrientationChangedCalled); -} - -- (void)testOrientationUpdateMustBeOnCaptureSessionQueue { - XCTestExpectation *queueExpectation = [self - expectationWithDescription:@"Orientation update must happen on the capture session queue"]; - - CameraPlugin *plugin = - [[CameraPlugin alloc] initWithRegistry:[[MockFlutterTextureRegistry alloc] init] - messenger:[[MockFlutterBinaryMessenger alloc] init]]; - const char *captureSessionQueueSpecific = "capture_session_queue"; - dispatch_queue_set_specific(plugin.captureSessionQueue, captureSessionQueueSpecific, - (void *)captureSessionQueueSpecific, NULL); - plugin.camera = _camera; - - _camera.setDeviceOrientationStub = ^(UIDeviceOrientation orientation) { - if (dispatch_get_specific(captureSessionQueueSpecific)) { - [queueExpectation fulfill]; - } - }; - - [plugin orientationChanged: - [self createMockNotificationForOrientation:UIDeviceOrientationLandscapeLeft]]; - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testOrientationChanged_noRetainCycle { - dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); - - __weak CameraPlugin *weakPlugin; - __weak MockCaptureDevice *weakDevice = _mockDevice; - - @autoreleasepool { - CameraPlugin *plugin = - [[CameraPlugin alloc] initWithRegistry:[[MockFlutterTextureRegistry alloc] init] - messenger:[[MockFlutterBinaryMessenger alloc] init] - globalAPI:_eventAPI - deviceDiscoverer:_deviceDiscoverer - deviceFactory:^NSObject *(NSString *name) { - return weakDevice; - } - captureSessionFactory:^NSObject *_Nonnull { - return [[MockCaptureSession alloc] init]; - } - captureDeviceInputFactory:[[MockCaptureDeviceInputFactory alloc] init]]; - weakPlugin = plugin; - plugin.captureSessionQueue = captureSessionQueue; - plugin.camera = _camera; - - [plugin orientationChanged: - [self createMockNotificationForOrientation:UIDeviceOrientationLandscapeLeft]]; - } - - // Sanity check - XCTAssertNil(weakPlugin, @"Camera must have been deallocated."); - - __block BOOL setDeviceOrientationCalled = NO; - _camera.setDeviceOrientationStub = ^(UIDeviceOrientation orientation) { - if (orientation == UIDeviceOrientationLandscapeLeft) { - setDeviceOrientationCalled = YES; - } - }; - - __weak MockGlobalEventApi *weakEventAPI = _eventAPI; - - // Must check in captureSessionQueue since orientationChanged dispatches to this queue. - XCTestExpectation *expectation = - [self expectationWithDescription:@"Dispatched to capture session queue"]; - dispatch_async(captureSessionQueue, ^{ - XCTAssertFalse(setDeviceOrientationCalled); - XCTAssertFalse(weakEventAPI.deviceOrientationChangedCalled); - [expectation fulfill]; - }); - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (NSNotification *)createMockNotificationForOrientation:(UIDeviceOrientation)deviceOrientation { - MockUIDevice *mockDevice = [[MockUIDevice alloc] init]; - mockDevice.mockOrientation = deviceOrientation; - - return [NSNotification notificationWithName:@"orientation_test" object:mockDevice]; -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift new file mode 100644 index 00000000000..65ed906e137 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift @@ -0,0 +1,169 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// 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 + +fileprivate final class MockCamera: FLTCam { + var setDeviceOrientationStub: ((UIDeviceOrientation) -> Void)? + + override func setDeviceOrientation(_ orientation: UIDeviceOrientation) { + setDeviceOrientationStub?(orientation) + } + + func setCaptureDevice(_ device: FLTCaptureDevice) { + self.captureDevice = device + } +} + +fileprivate final class MockUIDevice: UIDevice { + var mockOrientation: UIDeviceOrientation = .unknown + + override var orientation: UIDeviceOrientation { + return mockOrientation + } +} + +final class CameraOrientationTests: XCTestCase { + private var mockCamera: MockCamera! + private var mockDevice: MockCaptureDevice! + private var mockEventAPI: MockGlobalEventApi! + private var mockDeviceDiscoverer: MockCameraDeviceDiscoverer! + private var cameraPlugin: CameraPlugin! + + override func setUp() { + mockDevice = MockCaptureDevice() + mockCamera = MockCamera() + mockEventAPI = MockGlobalEventApi() + mockDeviceDiscoverer = MockCameraDeviceDiscoverer() + + cameraPlugin = CameraPlugin( + registry: MockFlutterTextureRegistry(), + messenger: MockFlutterBinaryMessenger(), + globalAPI: mockEventAPI, + deviceDiscoverer: mockDeviceDiscoverer, + deviceFactory: { _ in self.mockDevice }, + captureSessionFactory: { MockCaptureSession() }, + captureDeviceInputFactory: MockCaptureDeviceInputFactory() + ) + cameraPlugin.camera = mockCamera + } + + private func waitForRoundTrip(with queue: DispatchQueue) { + let expectation = self.expectation(description: "Queue flush") + queue.async { + DispatchQueue.main.async { + expectation.fulfill() + } + } + waitForExpectations(timeout: 30, handler: nil) + } + + private func sendOrientation(_ orientation: UIDeviceOrientation, to cameraPlugin: CameraPlugin) { + cameraPlugin.orientationChanged(createMockNotification(for: orientation)) + waitForRoundTrip(with: cameraPlugin.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() { + sendOrientation(.portraitUpsideDown, to: cameraPlugin) + XCTAssertEqual(mockEventAPI.lastOrientation, .portraitDown) + sendOrientation(.portrait, to: cameraPlugin) + XCTAssertEqual(mockEventAPI.lastOrientation, .portraitUp) + sendOrientation(.landscapeLeft, to: cameraPlugin) + XCTAssertEqual(mockEventAPI.lastOrientation, .landscapeLeft) + sendOrientation(.landscapeRight, to: cameraPlugin) + XCTAssertEqual(mockEventAPI.lastOrientation, .landscapeRight) + } + + func testOrientationNotificationsNotCalledForFaceUp() { + sendOrientation(.faceUp, to: cameraPlugin) + XCTAssertFalse(mockEventAPI.deviceOrientationChangedCalled) + } + + func testOrientationNotificationsNotCalledForFaceDown() { + sendOrientation(.faceDown, to: cameraPlugin) + XCTAssertFalse(mockEventAPI.deviceOrientationChangedCalled) + } + + func testOrientationUpdateMustBeOnCaptureSessionQueue() { + let queueExpectation = expectation( + description: "Orientation update must happen on the capture session queue") + + let plugin = CameraPlugin( + registry: MockFlutterTextureRegistry(), + messenger: MockFlutterBinaryMessenger() + ) + let captureSessionQueueSpecific = DispatchSpecificKey() + plugin.captureSessionQueue.setSpecific(key: captureSessionQueueSpecific, value: ()) + plugin.camera = mockCamera + + mockCamera.setDeviceOrientationStub = { orientation in + if DispatchQueue.getSpecific(key: captureSessionQueueSpecific) != nil { + queueExpectation.fulfill() + } + } + + plugin.orientationChanged(createMockNotification(for: .landscapeLeft)) + waitForExpectations(timeout: 30, handler: nil) + } + + func testOrientationChanged_noRetainCycle() { + let captureSessionQueue = DispatchQueue(label: "capture_session_queue") + weak var weakPlugin: CameraPlugin? + weak var weakDevice: MockCaptureDevice! = mockDevice + + autoreleasepool { + let plugin = CameraPlugin( + registry: MockFlutterTextureRegistry(), + messenger: MockFlutterBinaryMessenger(), + globalAPI: mockEventAPI, + deviceDiscoverer: mockDeviceDiscoverer, + deviceFactory: { _ in weakDevice }, + captureSessionFactory: { MockCaptureSession() }, + captureDeviceInputFactory: MockCaptureDeviceInputFactory() + ) + weakPlugin = plugin + plugin.captureSessionQueue = captureSessionQueue + plugin.camera = mockCamera + + plugin.orientationChanged(createMockNotification(for: .landscapeLeft)) + } + + // Sanity check. + let cameraDeallocatedExpectation = self.expectation( + description: "Camera must have been deallocated.") + captureSessionQueue.async { + XCTAssertNil(weakPlugin) + cameraDeallocatedExpectation.fulfill() + } + 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) + } +} diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h index 6e22ad5b1ac..eed847ea285 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h @@ -18,7 +18,8 @@ NS_ASSUME_NONNULL_BEGIN /// A class that manages camera's state and performs camera operations. @interface FLTCam : NSObject -@property(readonly, nonatomic) NSObject *captureDevice; +// FIXME It was readonly before +@property(nonatomic) NSObject *captureDevice; @property(readonly, nonatomic) CGSize previewSize; @property(assign, nonatomic) BOOL isPreviewPaused; @property(nonatomic, copy) void (^onFrameAvailable)(void); From 2608e456f0d17e99d75e7270ba96a788d1b9c067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Wed, 12 Feb 2025 14:43:41 +0100 Subject: [PATCH 04/40] Migrate CameraSettingsTests to Swift --- .../RunnerTests/CameraOrientationTests.swift | 6 +- .../ios/RunnerTests/CameraSettingsTests.m | 237 ------------------ .../ios/RunnerTests/CameraSettingsTests.swift | 203 +++++++++++++++ .../FLTCamMediaSettingsAVWrapper.h | 7 +- 4 files changed, 210 insertions(+), 243 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift index 65ed906e137..5c8e5bbd940 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift @@ -8,7 +8,7 @@ import XCTest @testable import camera_avfoundation -fileprivate final class MockCamera: FLTCam { +private final class MockCamera: FLTCam { var setDeviceOrientationStub: ((UIDeviceOrientation) -> Void)? override func setDeviceOrientation(_ orientation: UIDeviceOrientation) { @@ -20,7 +20,7 @@ fileprivate final class MockCamera: FLTCam { } } -fileprivate final class MockUIDevice: UIDevice { +private final class MockUIDevice: UIDevice { var mockOrientation: UIDeviceOrientation = .unknown override var orientation: UIDeviceOrientation { @@ -34,7 +34,7 @@ final class CameraOrientationTests: XCTestCase { private var mockEventAPI: MockGlobalEventApi! private var mockDeviceDiscoverer: MockCameraDeviceDiscoverer! private var cameraPlugin: CameraPlugin! - + override func setUp() { mockDevice = MockCaptureDevice() mockCamera = MockCamera() diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m deleted file mode 100644 index fb07cc3c9d8..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import XCTest; -@import AVFoundation; - -#import "CameraTestUtils.h" -#import "MockCameraDeviceDiscoverer.h" -#import "MockCaptureDevice.h" -#import "MockCaptureSession.h" -#import "MockFlutterBinaryMessenger.h" -#import "MockFlutterTextureRegistry.h" -#import "MockGlobalEventApi.h" - -static const FCPPlatformResolutionPreset gTestResolutionPreset = FCPPlatformResolutionPresetMedium; -static const int gTestFramesPerSecond = 15; -static const int gTestVideoBitrate = 200000; -static const int gTestAudioBitrate = 32000; -static const BOOL gTestEnableAudio = YES; - -@interface CameraSettingsTests : XCTestCase -@end - -/** - * A test implemetation of `FLTCamMediaSettingsAVWrapper` - * - * This xctest-expectation-checking implementation of `FLTCamMediaSettingsAVWrapper` is injected - * into `camera-avfoundation` plugin instead of real AVFoundation-based realization. - * Such kind of Dependency Injection (DI) allows to run media-settings tests without - * any additional mocking of AVFoundation classes. - */ -@interface TestMediaSettingsAVWrapper : FLTCamMediaSettingsAVWrapper -@property(nonatomic, readonly) XCTestExpectation *lockExpectation; -@property(nonatomic, readonly) XCTestExpectation *unlockExpectation; -@property(nonatomic, readonly) XCTestExpectation *minFrameDurationExpectation; -@property(nonatomic, readonly) XCTestExpectation *maxFrameDurationExpectation; -@property(nonatomic, readonly) XCTestExpectation *beginConfigurationExpectation; -@property(nonatomic, readonly) XCTestExpectation *commitConfigurationExpectation; -@property(nonatomic, readonly) XCTestExpectation *audioSettingsExpectation; -@property(nonatomic, readonly) XCTestExpectation *videoSettingsExpectation; -@end - -@implementation TestMediaSettingsAVWrapper - -- (instancetype)initWithTestCase:(XCTestCase *)test { - _lockExpectation = [test expectationWithDescription:@"lockExpectation"]; - _unlockExpectation = [test expectationWithDescription:@"unlockExpectation"]; - _minFrameDurationExpectation = [test expectationWithDescription:@"minFrameDurationExpectation"]; - _maxFrameDurationExpectation = [test expectationWithDescription:@"maxFrameDurationExpectation"]; - _beginConfigurationExpectation = - [test expectationWithDescription:@"beginConfigurationExpectation"]; - _commitConfigurationExpectation = - [test expectationWithDescription:@"commitConfigurationExpectation"]; - _audioSettingsExpectation = [test expectationWithDescription:@"audioSettingsExpectation"]; - _videoSettingsExpectation = [test expectationWithDescription:@"videoSettingsExpectation"]; - - return self; -} - -- (BOOL)lockDevice:(AVCaptureDevice *)captureDevice error:(NSError **)outError { - [_lockExpectation fulfill]; - return YES; -} - -- (void)unlockDevice:(AVCaptureDevice *)captureDevice { - [_unlockExpectation fulfill]; -} - -- (void)beginConfigurationForSession:(NSObject *)videoCaptureSession { - [_beginConfigurationExpectation fulfill]; -} - -- (void)commitConfigurationForSession:(NSObject *)videoCaptureSession { - [_commitConfigurationExpectation fulfill]; -} - -- (void)setMinFrameDuration:(CMTime)duration onDevice:(AVCaptureDevice *)captureDevice { - // FLTCam allows to set frame rate with 1/10 precision. - CMTime expectedDuration = CMTimeMake(10, gTestFramesPerSecond * 10); - - if (duration.value == expectedDuration.value && - duration.timescale == expectedDuration.timescale) { - [_minFrameDurationExpectation fulfill]; - } -} - -- (void)setMaxFrameDuration:(CMTime)duration onDevice:(AVCaptureDevice *)captureDevice { - // FLTCam allows to set frame rate with 1/10 precision. - CMTime expectedDuration = CMTimeMake(10, gTestFramesPerSecond * 10); - - if (duration.value == expectedDuration.value && - duration.timescale == expectedDuration.timescale) { - [_maxFrameDurationExpectation fulfill]; - } -} - -- (AVAssetWriterInput *)assetWriterAudioInputWithOutputSettings: - (nullable NSDictionary *)outputSettings { - if ([outputSettings[AVEncoderBitRateKey] isEqual:@(gTestAudioBitrate)]) { - [_audioSettingsExpectation fulfill]; - } - - return [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio - outputSettings:outputSettings]; -} - -- (AVAssetWriterInput *)assetWriterVideoInputWithOutputSettings: - (nullable NSDictionary *)outputSettings { - if ([outputSettings[AVVideoCompressionPropertiesKey] isKindOfClass:[NSMutableDictionary class]]) { - NSDictionary *compressionProperties = outputSettings[AVVideoCompressionPropertiesKey]; - - if ([compressionProperties[AVVideoAverageBitRateKey] isEqual:@(gTestVideoBitrate)] && - [compressionProperties[AVVideoExpectedSourceFrameRateKey] - isEqual:@(gTestFramesPerSecond)]) { - [_videoSettingsExpectation fulfill]; - } - } - - return [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo - outputSettings:outputSettings]; -} - -- (void)addInput:(AVAssetWriterInput *)writerInput toAssetWriter:(AVAssetWriter *)writer { -} - -- (NSDictionary *) - recommendedVideoSettingsForAssetWriterWithFileType:(AVFileType)fileType - forOutput:(AVCaptureVideoDataOutput *)output { - return @{}; -} - -@end - -@implementation CameraSettingsTests - -/// Expect that FPS, video and audio bitrate are passed to camera device and asset writer. -- (void)testSettings_shouldPassConfigurationToCameraDeviceAndWriter { - FCPPlatformMediaSettings *settings = - [FCPPlatformMediaSettings makeWithResolutionPreset:gTestResolutionPreset - framesPerSecond:@(gTestFramesPerSecond) - videoBitrate:@(gTestVideoBitrate) - audioBitrate:@(gTestAudioBitrate) - enableAudio:gTestEnableAudio]; - TestMediaSettingsAVWrapper *injectedWrapper = - [[TestMediaSettingsAVWrapper alloc] initWithTestCase:self]; - - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.mediaSettingsWrapper = injectedWrapper; - configuration.mediaSettings = settings; - FLTCam *camera = FLTCreateCamWithConfiguration(configuration); - - // Expect FPS configuration is passed to camera device. - [self waitForExpectations:@[ - injectedWrapper.lockExpectation, injectedWrapper.beginConfigurationExpectation, - injectedWrapper.minFrameDurationExpectation, injectedWrapper.maxFrameDurationExpectation, - injectedWrapper.commitConfigurationExpectation, injectedWrapper.unlockExpectation - ] - timeout:1 - enforceOrder:YES]; - - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - [self waitForExpectations:@[ - injectedWrapper.audioSettingsExpectation, injectedWrapper.videoSettingsExpectation - ] - timeout:1]; -} - -- (void)testSettings_ShouldBeSupportedByMethodCall { - MockCaptureDevice *mockDevice = [[MockCaptureDevice alloc] init]; - MockCaptureSession *mockSession = [[MockCaptureSession alloc] init]; - mockSession.canSetSessionPreset = YES; - - CameraPlugin *camera = - [[CameraPlugin alloc] initWithRegistry:[[MockFlutterTextureRegistry alloc] init] - messenger:[[MockFlutterBinaryMessenger alloc] init] - globalAPI:[[MockGlobalEventApi alloc] init] - deviceDiscoverer:[[MockCameraDeviceDiscoverer alloc] init] - deviceFactory:^NSObject *(NSString *name) { - return mockDevice; - } - captureSessionFactory:^NSObject *_Nonnull { - return mockSession; - } - captureDeviceInputFactory:[[MockCaptureDeviceInputFactory alloc] init]]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"]; - - // Set up method call - FCPPlatformMediaSettings *mediaSettings = - [FCPPlatformMediaSettings makeWithResolutionPreset:gTestResolutionPreset - framesPerSecond:@(gTestFramesPerSecond) - videoBitrate:@(gTestVideoBitrate) - audioBitrate:@(gTestAudioBitrate) - enableAudio:gTestEnableAudio]; - - __block NSNumber *resultValue; - [camera createCameraOnSessionQueueWithName:@"acamera" - settings:mediaSettings - completion:^(NSNumber *result, FlutterError *error) { - XCTAssertNil(error); - resultValue = result; - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:30 handler:nil]; - - // Verify the result - XCTAssertNotNil(resultValue); -} - -- (void)testSettings_ShouldSelectFormatWhichSupports60FPS { - FCPPlatformMediaSettings *settings = - [FCPPlatformMediaSettings makeWithResolutionPreset:gTestResolutionPreset - framesPerSecond:@(60) - videoBitrate:@(gTestVideoBitrate) - audioBitrate:@(gTestAudioBitrate) - enableAudio:gTestEnableAudio]; - - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.mediaSettings = settings; - FLTCam *camera = FLTCreateCamWithConfiguration(configuration); - - NSObject *range = - camera.captureDevice.activeFormat.videoSupportedFrameRateRanges[0]; - XCTAssertLessThanOrEqual(range.minFrameRate, 60); - XCTAssertGreaterThanOrEqual(range.maxFrameRate, 60); -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift new file mode 100644 index 00000000000..39c1c9e4919 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift @@ -0,0 +1,203 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import AVFoundation +import XCTest + +@testable import camera_avfoundation + +private let testResolutionPreset = FCPPlatformResolutionPreset.medium +private let testFramesPerSecond = 15 +private let testVideoBitrate = 200000 +private let testAudioBitrate = 32000 +private let testEnableAudio = true + +private final class TestMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { + let lockExpectation: XCTestExpectation + let unlockExpectation: XCTestExpectation + let minFrameDurationExpectation: XCTestExpectation + let maxFrameDurationExpectation: XCTestExpectation + let beginConfigurationExpectation: XCTestExpectation + let commitConfigurationExpectation: XCTestExpectation + let audioSettingsExpectation: XCTestExpectation + let videoSettingsExpectation: XCTestExpectation + + init(test: XCTestCase) { + lockExpectation = test.expectation(description: "lockExpectation") + unlockExpectation = test.expectation(description: "unlockExpectation") + minFrameDurationExpectation = test.expectation(description: "minFrameDurationExpectation") + maxFrameDurationExpectation = test.expectation(description: "maxFrameDurationExpectation") + beginConfigurationExpectation = test.expectation(description: "beginConfigurationExpectation") + commitConfigurationExpectation = test.expectation(description: "commitConfigurationExpectation") + audioSettingsExpectation = test.expectation(description: "audioSettingsExpectation") + videoSettingsExpectation = test.expectation(description: "videoSettingsExpectation") + } + + override func lockDevice(_ captureDevice: any FLTCaptureDevice) throws { + lockExpectation.fulfill() + } + + override func unlockDevice(_ captureDevice: any FLTCaptureDevice) { + unlockExpectation.fulfill() + } + + override func beginConfiguration(for videoCaptureSession: any FLTCaptureSession) { + beginConfigurationExpectation.fulfill() + } + + override func commitConfiguration(for videoCaptureSession: any FLTCaptureSession) { + commitConfigurationExpectation.fulfill() + } + + override func setMinFrameDuration(_ duration: CMTime, on captureDevice: any FLTCaptureDevice) { + // FLTCam allows to set frame rate with 1/10 precision. + let expectedDuration = CMTimeMake(value: 10, timescale: Int32(testFramesPerSecond * 10)) + if duration == expectedDuration { + minFrameDurationExpectation.fulfill() + } + } + + override func setMaxFrameDuration(_ duration: CMTime, on captureDevice: any FLTCaptureDevice) { + // FLTCam allows to set frame rate with 1/10 precision. + let expectedDuration = CMTimeMake(value: 10, timescale: Int32(testFramesPerSecond * 10)) + if duration == expectedDuration { + maxFrameDurationExpectation.fulfill() + } + } + + override func assetWriterAudioInput(withOutputSettings outputSettings: [String: Any]?) + -> AVAssetWriterInput + { + if let bitrate = outputSettings?[AVEncoderBitRateKey] as? Int, bitrate == testAudioBitrate { + audioSettingsExpectation.fulfill() + } + return AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings) + } + + override func assetWriterVideoInput(withOutputSettings outputSettings: [String: Any]?) + -> AVAssetWriterInput + { + if let compressionProperties = outputSettings?[AVVideoCompressionPropertiesKey] + as? [String: Any], + let bitrate = compressionProperties[AVVideoAverageBitRateKey] as? Int, + let frameRate = compressionProperties[AVVideoExpectedSourceFrameRateKey] as? Int, + bitrate == testVideoBitrate, frameRate == testFramesPerSecond + { + videoSettingsExpectation.fulfill() + } + + // AVAssetWriterInput needs these three keys, otherwise it throws. + var outputSettingsWithRequiredKeys = outputSettings ?? [:] + outputSettingsWithRequiredKeys[AVVideoCodecKey] = AVVideoCodecType.h264 + outputSettingsWithRequiredKeys[AVVideoWidthKey] = 1280 + outputSettingsWithRequiredKeys[AVVideoHeightKey] = 720 + + return AVAssetWriterInput(mediaType: .video, outputSettings: outputSettingsWithRequiredKeys) + } + + override func addInput(_ writerInput: AVAssetWriterInput, to writer: AVAssetWriter) { + // No-op + } + + override func recommendedVideoSettingsForAssetWriter( + withFileType fileType: AVFileType, for output: AVCaptureVideoDataOutput + ) -> [String: Any]? { + return [:] + } + +} + +final class CameraSettingsTests: XCTestCase { + func testSettings_shouldPassConfigurationToCameraDeviceAndWriter() { + let settings = FCPPlatformMediaSettings.make( + with: testResolutionPreset, + framesPerSecond: NSNumber(value: testFramesPerSecond), + videoBitrate: NSNumber(value: testVideoBitrate), + audioBitrate: NSNumber(value: testAudioBitrate), + enableAudio: testEnableAudio + ) + let injectedWrapper = TestMediaSettingsAVWrapper(test: self) + + let configuration = FLTCreateTestCameraConfiguration() + configuration.mediaSettingsWrapper = injectedWrapper + configuration.mediaSettings = settings + let camera = FLTCreateCamWithConfiguration(configuration) + + // Expect FPS configuration is passed to camera device. + wait( + for: [ + injectedWrapper.lockExpectation, + injectedWrapper.beginConfigurationExpectation, + injectedWrapper.minFrameDurationExpectation, + injectedWrapper.maxFrameDurationExpectation, + injectedWrapper.commitConfigurationExpectation, + injectedWrapper.unlockExpectation, + ], timeout: 1, enforceOrder: true) + + camera.startVideoRecording( + completion: { error in + // No-op. + }, messengerForStreaming: nil) + + wait( + for: [ + injectedWrapper.audioSettingsExpectation, + injectedWrapper.videoSettingsExpectation, + ], timeout: 1) + } + + func testSettings_ShouldBeSupportedByMethodCall() { + let mockDevice = MockCaptureDevice() + let mockSession = MockCaptureSession() + mockSession.canSetSessionPreset = true + let camera = CameraPlugin( + registry: MockFlutterTextureRegistry(), + messenger: MockFlutterBinaryMessenger(), + globalAPI: MockGlobalEventApi(), + deviceDiscoverer: MockCameraDeviceDiscoverer(), + deviceFactory: { _ in mockDevice }, + captureSessionFactory: { mockSession }, + captureDeviceInputFactory: MockCaptureDeviceInputFactory() + ) + + let expectation = self.expectation(description: "Result finished") + let mediaSettings = FCPPlatformMediaSettings.make( + with: testResolutionPreset, + framesPerSecond: NSNumber(value: testFramesPerSecond), + videoBitrate: NSNumber(value: testVideoBitrate), + audioBitrate: NSNumber(value: testAudioBitrate), + enableAudio: testEnableAudio + ) + var resultValue: NSNumber? + camera.createCameraOnSessionQueue( + withName: "acamera", + settings: mediaSettings + ) { result, error in + XCTAssertNil(error) + resultValue = result + expectation.fulfill() + } + + waitForExpectations(timeout: 30, handler: nil) + XCTAssertNotNil(resultValue) + } + + func testSettings_ShouldSelectFormatWhichSupports60FPS() { + let settings = FCPPlatformMediaSettings.make( + with: testResolutionPreset, + framesPerSecond: NSNumber(value: 60), + videoBitrate: NSNumber(value: testVideoBitrate), + audioBitrate: NSNumber(value: testAudioBitrate), + enableAudio: testEnableAudio + ) + + let configuration = FLTCreateTestCameraConfiguration() + configuration.mediaSettings = settings + let camera = FLTCreateCamWithConfiguration(configuration) + + let range = camera.captureDevice.activeFormat().videoSupportedFrameRateRanges[0] + XCTAssertLessThanOrEqual(range.minFrameRate, 60) + XCTAssertGreaterThanOrEqual(range.maxFrameRate, 60) + } +} diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCamMediaSettingsAVWrapper.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCamMediaSettingsAVWrapper.h index fb8d7040d67..b890cffa1c1 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCamMediaSettingsAVWrapper.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCamMediaSettingsAVWrapper.h @@ -29,14 +29,14 @@ NS_ASSUME_NONNULL_BEGIN * @result A BOOL indicating whether the device was successfully locked for configuration. */ - (BOOL)lockDevice:(NSObject *)captureDevice - error:(NSError *_Nullable *_Nullable)outError; + error:(NSError *_Nullable *_Nullable)outError NS_SWIFT_NAME(lockDevice(_:)); /** * @method unlockDevice: * @abstract Release exclusive control over device hardware properties. * @param captureDevice The capture device. */ -- (void)unlockDevice:(NSObject *)captureDevice; +- (void)unlockDevice:(NSObject *)captureDevice NS_SWIFT_NAME(unlockDevice(_:)) ; /** * @method beginConfigurationForSession: @@ -98,7 +98,8 @@ NS_ASSUME_NONNULL_BEGIN * @param writerInput The `AVAssetWriterInput` object to be added. * @param writer The `AVAssetWriter` object. */ -- (void)addInput:(AVAssetWriterInput *)writerInput toAssetWriter:(AVAssetWriter *)writer; +- (void)addInput:(AVAssetWriterInput *)writerInput + toAssetWriter:(AVAssetWriter *)writer NS_SWIFT_NAME(addInput(_:to:)) ; /** * @method recommendedVideoSettingsForAssetWriterWithFileType:forOutput: From f261951a7b6c9edfa2b8d1796b652e761db1b44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Wed, 12 Feb 2025 15:30:42 +0100 Subject: [PATCH 05/40] Migrate CameraSessionPresetsTests to Swift --- .../ios/Runner.xcodeproj/project.pbxproj | 16 +-- .../RunnerTests/CameraSessionPresetsTests.m | 119 ------------------ .../CameraSessionPresetsTests.swift | 91 ++++++++++++++ .../RunnerTests/RunnerTests-Bridging-Header.h | 1 + 4 files changed, 100 insertions(+), 127 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.swift diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index b30efcec445..d31bab4ec40 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 54D650172516862D30686934 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ECAF63F924EFA2D68883BA85 /* libPods-Runner.a */; }; 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 788A065927B0E02900533D74 /* StreamingTest.m */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - 7D5FCCD42AEF9D0200FB7108 /* CameraSettingsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D5FCCD32AEF9D0200FB7108 /* CameraSettingsTests.m */; }; 7F29EB222D269ED500740257 /* MockEventChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F29EB212D269ED500740257 /* MockEventChannel.m */; }; 7F29EB292D26A59000740257 /* MockCameraDeviceDiscoverer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F29EB282D26A59000740257 /* MockCameraDeviceDiscoverer.m */; }; 7F29EB412D281C7E00740257 /* MockCaptureSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F29EB402D281C7E00740257 /* MockCaptureSession.m */; }; @@ -34,11 +33,12 @@ 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */; }; + 97BD4A0E2D5CC5AE00F857D5 /* CameraSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */; }; + 97BD4A102D5CE13500F857D5 /* CameraSessionPresetsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */; }; E01EE4A82799F3A5008C1950 /* QueueUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */; }; E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */; }; E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */; }; @@ -85,7 +85,6 @@ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 7D5FCCD32AEF9D0200FB7108 /* CameraSettingsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraSettingsTests.m; sourceTree = ""; }; 7F29EB202D269E4300740257 /* MockEventChannel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockEventChannel.h; sourceTree = ""; }; 7F29EB212D269ED500740257 /* MockEventChannel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockEventChannel.m; sourceTree = ""; }; 7F29EB272D26A55300740257 /* MockCameraDeviceDiscoverer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCameraDeviceDiscoverer.h; sourceTree = ""; }; @@ -115,6 +114,8 @@ 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCaptureSessionQueueRaceConditionTests.swift; sourceTree = ""; }; 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraMethodChannelTests.swift; sourceTree = ""; }; 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraOrientationTests.swift; sourceTree = ""; }; + 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSettingsTests.swift; sourceTree = ""; }; + 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSessionPresetsTests.swift; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -124,7 +125,6 @@ 9DDC4CE84A8B378AE4A8CD9C /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A8F314CD1C64E9257EBC811D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; B61D98BBC8FB276D1C4A7BB2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = CameraSessionPresetsTests.m; sourceTree = ""; }; E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QueueUtilsTests.m; sourceTree = ""; }; E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTSavePhotoDelegateTests.m; sourceTree = ""; }; E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCamPhotoCaptureTests.m; sourceTree = ""; }; @@ -165,7 +165,6 @@ isa = PBXGroup; children = ( 7F29EB3F2D281C6D00740257 /* Mocks */, - 7D5FCCD32AEF9D0200FB7108 /* CameraSettingsTests.m */, 03BB766A2665316900CE5A93 /* CameraFocusTests.m */, 7FA99E582D22C75300582559 /* CameraExposureTests.m */, 03BB766C2665316900CE5A93 /* Info.plist */, @@ -181,11 +180,12 @@ E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */, 788A065927B0E02900533D74 /* StreamingTest.m */, 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */, - CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */, 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */, 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */, 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */, 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */, + 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */, + 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -517,8 +517,8 @@ E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */, 7F56D0382D1EDDCE005676A5 /* CameraPermissionTests.m in Sources */, E0F95E3D27A32AB900699390 /* CameraPropertiesTests.m in Sources */, + 97BD4A0E2D5CC5AE00F857D5 /* CameraSettingsTests.swift in Sources */, 03BB766B2665316900CE5A93 /* CameraFocusTests.m in Sources */, - 7D5FCCD42AEF9D0200FB7108 /* CameraSettingsTests.m in Sources */, 7F8FD2292D4BFABF001AF2C1 /* MockGlobalEventApi.m in Sources */, 7FA99E592D22C75300582559 /* CameraExposureTests.m in Sources */, E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */, @@ -531,9 +531,9 @@ 43ED1537282570DE00EB00DE /* AvailableCamerasTest.m in Sources */, E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */, 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */, - CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTests.m in Sources */, 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */, 7F29EB292D26A59000740257 /* MockCameraDeviceDiscoverer.m in Sources */, + 97BD4A102D5CE13500F857D5 /* CameraSessionPresetsTests.swift in Sources */, 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */, E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m deleted file mode 100644 index 14b4f2e9bcc..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif - -@import AVFoundation; -@import XCTest; - -#import "CameraTestUtils.h" -#import "MockCaptureDevice.h" -#import "MockCaptureDeviceFormat.h" -#import "MockCaptureSession.h" - -/// Includes test cases related to resolution presets setting operations for FLTCam class. -@interface FLTCamSessionPresetsTest : XCTestCase -@end - -@implementation FLTCamSessionPresetsTest - -- (void)testResolutionPresetWithBestFormat_mustUpdateCaptureSessionPreset { - NSString *expectedPreset = AVCaptureSessionPresetInputPriority; - XCTestExpectation *presetExpectation = [self expectationWithDescription:@"Expected preset set"]; - XCTestExpectation *lockForConfigurationExpectation = - [self expectationWithDescription:@"Expected lockForConfiguration called"]; - - MockCaptureSession *videoSessionMock = [[MockCaptureSession alloc] init]; - videoSessionMock.setSessionPresetStub = ^(NSString *preset) { - if (preset == expectedPreset) { - [presetExpectation fulfill]; - } - }; - - MockCaptureDeviceFormat *captureFormatMock = [[MockCaptureDeviceFormat alloc] init]; - - MockCaptureDevice *captureDeviceMock = [[MockCaptureDevice alloc] init]; - captureDeviceMock.formats = @[ captureFormatMock ]; - captureDeviceMock.activeFormat = captureFormatMock; - captureDeviceMock.lockForConfigurationStub = - ^BOOL(NSError *__autoreleasing _Nullable *_Nullable error) { - [lockForConfigurationExpectation fulfill]; - return YES; - }; - - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.captureDeviceFactory = ^NSObject *_Nonnull { - return captureDeviceMock; - }; - configuration.videoDimensionsForFormat = - ^CMVideoDimensions(NSObject *format) { - CMVideoDimensions videoDimensions; - videoDimensions.width = 1; - videoDimensions.height = 1; - return videoDimensions; - }; - configuration.videoCaptureSession = videoSessionMock; - configuration.mediaSettings = FCPGetDefaultMediaSettings(FCPPlatformResolutionPresetMax); - - FLTCreateCamWithConfiguration(configuration); - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testResolutionPresetWithCanSetSessionPresetMax_mustUpdateCaptureSessionPreset { - NSString *expectedPreset = AVCaptureSessionPreset3840x2160; - XCTestExpectation *expectation = [self expectationWithDescription:@"Expected preset set"]; - - MockCaptureSession *videoSessionMock = [[MockCaptureSession alloc] init]; - - // Make sure that setting resolution preset for session always succeeds. - videoSessionMock.canSetSessionPreset = YES; - - videoSessionMock.setSessionPresetStub = ^(NSString *preset) { - if (preset == expectedPreset) { - [expectation fulfill]; - } - }; - - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.videoCaptureSession = videoSessionMock; - configuration.mediaSettings = FCPGetDefaultMediaSettings(FCPPlatformResolutionPresetMax); - configuration.captureDeviceFactory = ^NSObject * { - return [[MockCaptureDevice alloc] init]; - }; - - FLTCreateCamWithConfiguration(configuration); - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSessionPreset { - NSString *expectedPreset = AVCaptureSessionPreset3840x2160; - XCTestExpectation *expectation = [self expectationWithDescription:@"Expected preset set"]; - - MockCaptureSession *videoSessionMock = [[MockCaptureSession alloc] init]; - - // Make sure that setting resolution preset for session always succeeds. - videoSessionMock.canSetSessionPreset = YES; - - // Expect that setting "ultraHigh" resolutionPreset correctly updates videoCaptureSession. - videoSessionMock.setSessionPresetStub = ^(NSString *preset) { - if (preset == expectedPreset) { - [expectation fulfill]; - } - }; - - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.videoCaptureSession = videoSessionMock; - configuration.mediaSettings = FCPGetDefaultMediaSettings(FCPPlatformResolutionPresetUltraHigh); - - FLTCreateCamWithConfiguration(configuration); - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.swift new file mode 100644 index 00000000000..3736f6aacf4 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.swift @@ -0,0 +1,91 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import AVFoundation +import XCTest + +@testable import camera_avfoundation + +/// Includes test cases related to resolution presets setting operations for FLTCam class. +final class CameraSessionPresetsTests: XCTestCase { + func testResolutionPresetWithBestFormat_mustUpdateCaptureSessionPreset() { + let expectedPreset = AVCaptureSession.Preset.inputPriority + let presetExpectation = expectation(description: "Expected preset set") + let lockForConfigurationExpectation = expectation( + description: "Expected lockForConfiguration called") + + let videoSessionMock = MockCaptureSession() + videoSessionMock.setSessionPresetStub = { preset in + if preset == expectedPreset { + presetExpectation.fulfill() + } + } + let captureFormatMock = MockCaptureDeviceFormat() + let captureDeviceMock = MockCaptureDevice() + captureDeviceMock.formats = [captureFormatMock] + captureDeviceMock.setActiveFormat(captureFormatMock) + captureDeviceMock.lockForConfigurationStub = { error in + lockForConfigurationExpectation.fulfill() + return true + } + + let configuration = FLTCreateTestCameraConfiguration() + configuration.captureDeviceFactory = { captureDeviceMock } + configuration.videoDimensionsForFormat = { format in + return CMVideoDimensions(width: 1, height: 1) + } + configuration.videoCaptureSession = videoSessionMock + configuration.mediaSettings = FCPGetDefaultMediaSettings(FCPPlatformResolutionPreset.max) + + FLTCreateCamWithConfiguration(configuration) + + waitForExpectations(timeout: 30, handler: nil) + } + + func testResolutionPresetWithCanSetSessionPresetMax_mustUpdateCaptureSessionPreset() { + let expectedPreset = AVCaptureSession.Preset.hd4K3840x2160 + let expectation = self.expectation(description: "Expected preset set") + + let videoSessionMock = MockCaptureSession() + // Make sure that setting resolution preset for session always succeeds. + videoSessionMock.canSetSessionPreset = true + videoSessionMock.setSessionPresetStub = { preset in + if preset == expectedPreset { + expectation.fulfill() + } + } + + let configuration = FLTCreateTestCameraConfiguration() + configuration.videoCaptureSession = videoSessionMock + configuration.mediaSettings = FCPGetDefaultMediaSettings(FCPPlatformResolutionPreset.max) + configuration.captureDeviceFactory = { MockCaptureDevice() } + + FLTCreateCamWithConfiguration(configuration) + + waitForExpectations(timeout: 30, handler: nil) + } + + func testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSessionPreset() { + let expectedPreset = AVCaptureSession.Preset.hd4K3840x2160 + let expectation = self.expectation(description: "Expected preset set") + + let videoSessionMock = MockCaptureSession() + // Make sure that setting resolution preset for session always succeeds. + videoSessionMock.canSetSessionPreset = true + // Expect that setting "ultraHigh" resolutionPreset correctly updates videoCaptureSession. + videoSessionMock.setSessionPresetStub = { preset in + if preset == expectedPreset { + expectation.fulfill() + } + } + + let configuration = FLTCreateTestCameraConfiguration() + configuration.videoCaptureSession = videoSessionMock + configuration.mediaSettings = FCPGetDefaultMediaSettings(FCPPlatformResolutionPreset.ultraHigh) + + FLTCreateCamWithConfiguration(configuration) + + waitForExpectations(timeout: 30, handler: nil) + } +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index 581002f9cf6..125bc086ecd 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -12,6 +12,7 @@ // Mocks, protocols. #import "MockCameraDeviceDiscoverer.h" #import "MockCaptureDevice.h" +#import "MockCaptureDeviceFormat.h" #import "MockCaptureSession.h" #import "MockDeviceOrientationProvider.h" #import "MockEventChannel.h" From effbaaea177a1529c1affa8df32f63fb6e70ee53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Tue, 11 Feb 2025 11:08:28 +0100 Subject: [PATCH 06/40] Bump version and update changelog --- packages/camera/camera_avfoundation/CHANGELOG.md | 4 ++++ packages/camera/camera_avfoundation/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 2388f101e24..6b2e63ff95e 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.18+5 + +* Migrates unit tests to Swift. + ## 0.9.18+4 * Refactors implementations to reduce usage of OCMock in internal testing. diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 56cd44cc6ac..2b858f7e30b 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.18+4 +version: 0.9.18+5 environment: sdk: ^3.4.0 From 145ccd270464958075376b487a8cd0304bf15dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Wed, 12 Feb 2025 18:48:27 +0100 Subject: [PATCH 07/40] Remove redundant "any" keywords --- .../ios/RunnerTests/CameraSettingsTests.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift index 39c1c9e4919..cd8f99a0ebe 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift @@ -34,23 +34,23 @@ private final class TestMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { videoSettingsExpectation = test.expectation(description: "videoSettingsExpectation") } - override func lockDevice(_ captureDevice: any FLTCaptureDevice) throws { + override func lockDevice(_ captureDevice: FLTCaptureDevice) throws { lockExpectation.fulfill() } - override func unlockDevice(_ captureDevice: any FLTCaptureDevice) { + override func unlockDevice(_ captureDevice: FLTCaptureDevice) { unlockExpectation.fulfill() } - override func beginConfiguration(for videoCaptureSession: any FLTCaptureSession) { + override func beginConfiguration(for videoCaptureSession: FLTCaptureSession) { beginConfigurationExpectation.fulfill() } - override func commitConfiguration(for videoCaptureSession: any FLTCaptureSession) { + override func commitConfiguration(for videoCaptureSession: FLTCaptureSession) { commitConfigurationExpectation.fulfill() } - override func setMinFrameDuration(_ duration: CMTime, on captureDevice: any FLTCaptureDevice) { + override func setMinFrameDuration(_ duration: CMTime, on captureDevice: FLTCaptureDevice) { // FLTCam allows to set frame rate with 1/10 precision. let expectedDuration = CMTimeMake(value: 10, timescale: Int32(testFramesPerSecond * 10)) if duration == expectedDuration { @@ -58,7 +58,7 @@ private final class TestMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { } } - override func setMaxFrameDuration(_ duration: CMTime, on captureDevice: any FLTCaptureDevice) { + override func setMaxFrameDuration(_ duration: CMTime, on captureDevice: FLTCaptureDevice) { // FLTCam allows to set frame rate with 1/10 precision. let expectedDuration = CMTimeMake(value: 10, timescale: Int32(testFramesPerSecond * 10)) if duration == expectedDuration { From 5464d73fd8cd86e7039cbbdeea0ed25bad936b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 11:19:53 +0100 Subject: [PATCH 08/40] Remove duplicated test file --- .../ios/RunnerTests/CameraExposureTests.m | 81 ------------------- 1 file changed, 81 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraExposureTests.m diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraExposureTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraExposureTests.m deleted file mode 100644 index 8b6b7964f9f..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraExposureTests.m +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -@import XCTest; -@import AVFoundation; - -#import "CameraTestUtils.h" -#import "MockCaptureDevice.h" -#import "MockDeviceOrientationProvider.h" - -@interface CameraExposureTests : XCTestCase -@property(readonly, nonatomic) FLTCam *camera; -@property(readonly, nonatomic) MockCaptureDevice *mockDevice; -@property(readonly, nonatomic) MockDeviceOrientationProvider *mockDeviceOrientationProvider; -@end - -@implementation CameraExposureTests - -- (void)setUp { - MockCaptureDevice *mockDevice = [[MockCaptureDevice alloc] init]; - _mockDeviceOrientationProvider = [[MockDeviceOrientationProvider alloc] init]; - _mockDevice = mockDevice; - - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.captureDeviceFactory = ^NSObject *_Nonnull { return mockDevice; }; - configuration.deviceOrientationProvider = _mockDeviceOrientationProvider; - _camera = FLTCreateCamWithConfiguration(configuration); -} - -- (void)testSetExposurePointWithResult_SetsExposurePointOfInterest { - // UI is currently in landscape left orientation - _mockDeviceOrientationProvider.orientation = UIDeviceOrientationLandscapeLeft; - // Exposure point of interest is supported - _mockDevice.exposurePointOfInterestSupported = YES; - - // Verify the focus point of interest has been set - __block CGPoint setPoint = CGPointZero; - _mockDevice.setExposurePointOfInterestStub = ^(CGPoint point) { - if (CGPointEqualToPoint(CGPointMake(1, 1), point)) { - setPoint = point; - } - }; - - // Run test - XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Completion called"]; - [_camera setExposurePoint:[FCPPlatformPoint makeWithX:1 y:1] - withCompletion:^(FlutterError *_Nullable error) { - XCTAssertNil(error); - [completionExpectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:30 handler:nil]; - XCTAssertEqual(setPoint.x, 1.0); - XCTAssertEqual(setPoint.y, 1.0); -} - -- (void)testSetExposurePoint_WhenNotSupported_ReturnsError { - // UI is currently in landscape left orientation - _mockDeviceOrientationProvider.orientation = UIDeviceOrientationLandscapeLeft; - // Exposure point of interest is not supported - _mockDevice.exposurePointOfInterestSupported = NO; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Completion with error"]; - - // Run - [_camera - setExposurePoint:[FCPPlatformPoint makeWithX:1 y:1] - withCompletion:^(FlutterError *_Nullable error) { - XCTAssertNotNil(error); - XCTAssertEqualObjects(error.code, @"setExposurePointFailed"); - XCTAssertEqualObjects(error.message, @"Device does not have exposure point capabilities"); - [expectation fulfill]; - }]; - - // Verify - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -@end From 39334e15e3d1cd5ca52a3186b970434260879367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 11:23:04 +0100 Subject: [PATCH 09/40] Inlcude missing test file --- .../ios/Runner.xcodeproj/project.pbxproj | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index 8beee77488f..ab37b8ed568 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -25,19 +25,20 @@ 7FD582122D579650003B1200 /* MockWritableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582112D579650003B1200 /* MockWritableData.m */; }; 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */; }; 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; - 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; - 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */; }; - 97BD4A0E2D5CC5AE00F857D5 /* CameraSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */; }; - 97BD4A102D5CE13500F857D5 /* CameraSessionPresetsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */; }; 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */; }; 972CA92D2D5A28C4004B846F /* QueueUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */; }; 972CA9312D5A366C004B846F /* CameraExposureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA9302D5A366C004B846F /* CameraExposureTests.swift */; }; 977A25202D5A439300931E34 /* AvailableCamerasTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */; }; 977A25222D5A49EC00931E34 /* CameraFocusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */; }; 977A25242D5A511600931E34 /* CameraPermissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25232D5A511600931E34 /* CameraPermissionTests.swift */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 979B3DFB2D5B6BC7009BDE1A /* ExceptionCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */; }; + 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; + 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; + 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */; }; + 97BD4A0E2D5CC5AE00F857D5 /* CameraSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */; }; + 97BD4A102D5CE13500F857D5 /* CameraSessionPresetsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */; }; + 97C0FFAC2D5DFF5700A36284 /* CameraUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C0FFAB2D5DFF5700A36284 /* CameraUtilTests.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -106,29 +107,29 @@ 7FD582212D579ED9003B1200 /* MockCapturePhotoOutput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCapturePhotoOutput.h; sourceTree = ""; }; 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureDeviceFormat.m; sourceTree = ""; }; 7FD582362D57D989003B1200 /* MockCaptureDeviceFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureDeviceFormat.h; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; - 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCaptureSessionQueueRaceConditionTests.swift; sourceTree = ""; }; - 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraMethodChannelTests.swift; sourceTree = ""; }; - 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraOrientationTests.swift; sourceTree = ""; }; - 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSettingsTests.swift; sourceTree = ""; }; - 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSessionPresetsTests.swift; sourceTree = ""; }; 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPropertiesTests.swift; sourceTree = ""; }; 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueUtilsTests.swift; sourceTree = ""; }; 972CA9302D5A366C004B846F /* CameraExposureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraExposureTests.swift; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvailableCamerasTests.swift; sourceTree = ""; }; 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraFocusTests.swift; sourceTree = ""; }; 977A25232D5A511600931E34 /* CameraPermissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPermissionTests.swift; sourceTree = ""; }; 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = ""; }; 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExceptionCatcher.m; sourceTree = ""; }; + 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; + 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCaptureSessionQueueRaceConditionTests.swift; sourceTree = ""; }; + 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraMethodChannelTests.swift; sourceTree = ""; }; + 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraOrientationTests.swift; sourceTree = ""; }; + 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSettingsTests.swift; sourceTree = ""; }; + 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSessionPresetsTests.swift; sourceTree = ""; }; + 97C0FFAB2D5DFF5700A36284 /* CameraUtilTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraUtilTests.m; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 97DB234B2D566D0600CEFE66 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; 97DB234C2D566D0700CEFE66 /* CameraPreviewPauseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPreviewPauseTests.swift; sourceTree = ""; }; 9DDC4CE84A8B378AE4A8CD9C /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A8F314CD1C64E9257EBC811D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; @@ -174,6 +175,7 @@ E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */, E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */, E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */, + 97C0FFAB2D5DFF5700A36284 /* CameraUtilTests.m */, E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */, E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */, 788A065927B0E02900533D74 /* StreamingTest.m */, @@ -550,6 +552,7 @@ 7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */, 7FCEDD362D43C2B900EA1CA8 /* MockCaptureDevice.m in Sources */, 7F8FD22C2D4D07DD001AF2C1 /* MockFlutterTextureRegistry.m in Sources */, + 97C0FFAC2D5DFF5700A36284 /* CameraUtilTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 6989e86f7900924745b8408addd85f78e602545b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 11:43:15 +0100 Subject: [PATCH 10/40] Bump version and update changelog --- packages/camera/camera_avfoundation/CHANGELOG.md | 4 ++++ packages/camera/camera_avfoundation/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 6b2e63ff95e..0cf3441927b 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.18+6 + +* Migrates unit tests to Swift. + ## 0.9.18+5 * Migrates unit tests to Swift. diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 2b858f7e30b..8ba6e0d0637 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.18+5 +version: 0.9.18+6 environment: sdk: ^3.4.0 From 672540930b015449bb10653d59aec1929fd4e629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 11:45:44 +0100 Subject: [PATCH 11/40] Resolve conflicts in bridging-header file --- .../example/ios/RunnerTests/RunnerTests-Bridging-Header.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index 4b7e2b430d2..f39781492b2 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -12,10 +12,7 @@ // Mocks, protocols. #import "MockCameraDeviceDiscoverer.h" #import "MockCaptureDevice.h" -<<<<<<< HEAD #import "MockCaptureDeviceFormat.h" -======= ->>>>>>> main #import "MockCaptureSession.h" #import "MockDeviceOrientationProvider.h" #import "MockEventChannel.h" @@ -25,7 +22,4 @@ // Utils. #import "CameraTestUtils.h" -<<<<<<< HEAD -======= #import "ExceptionCatcher.h" ->>>>>>> main From 0b51e01ac4910427880e02bc740f0e4d20628b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 12:31:27 +0100 Subject: [PATCH 12/40] Revert "Inlcude missing test file" This reverts commit 39334e15e3d1cd5ca52a3186b970434260879367. --- .../ios/Runner.xcodeproj/project.pbxproj | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index ab37b8ed568..8beee77488f 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -25,20 +25,19 @@ 7FD582122D579650003B1200 /* MockWritableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582112D579650003B1200 /* MockWritableData.m */; }; 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */; }; 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; + 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; + 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */; }; + 97BD4A0E2D5CC5AE00F857D5 /* CameraSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */; }; + 97BD4A102D5CE13500F857D5 /* CameraSessionPresetsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */; }; 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */; }; 972CA92D2D5A28C4004B846F /* QueueUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */; }; 972CA9312D5A366C004B846F /* CameraExposureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA9302D5A366C004B846F /* CameraExposureTests.swift */; }; 977A25202D5A439300931E34 /* AvailableCamerasTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */; }; 977A25222D5A49EC00931E34 /* CameraFocusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */; }; 977A25242D5A511600931E34 /* CameraPermissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25232D5A511600931E34 /* CameraPermissionTests.swift */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 979B3DFB2D5B6BC7009BDE1A /* ExceptionCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */; }; - 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; - 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; - 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */; }; - 97BD4A0E2D5CC5AE00F857D5 /* CameraSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */; }; - 97BD4A102D5CE13500F857D5 /* CameraSessionPresetsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */; }; - 97C0FFAC2D5DFF5700A36284 /* CameraUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C0FFAB2D5DFF5700A36284 /* CameraUtilTests.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -107,29 +106,29 @@ 7FD582212D579ED9003B1200 /* MockCapturePhotoOutput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCapturePhotoOutput.h; sourceTree = ""; }; 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureDeviceFormat.m; sourceTree = ""; }; 7FD582362D57D989003B1200 /* MockCaptureDeviceFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureDeviceFormat.h; sourceTree = ""; }; - 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPropertiesTests.swift; sourceTree = ""; }; - 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueUtilsTests.swift; sourceTree = ""; }; - 972CA9302D5A366C004B846F /* CameraExposureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraExposureTests.swift; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvailableCamerasTests.swift; sourceTree = ""; }; - 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraFocusTests.swift; sourceTree = ""; }; - 977A25232D5A511600931E34 /* CameraPermissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPermissionTests.swift; sourceTree = ""; }; - 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = ""; }; - 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExceptionCatcher.m; sourceTree = ""; }; 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCaptureSessionQueueRaceConditionTests.swift; sourceTree = ""; }; 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraMethodChannelTests.swift; sourceTree = ""; }; 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraOrientationTests.swift; sourceTree = ""; }; 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSettingsTests.swift; sourceTree = ""; }; 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSessionPresetsTests.swift; sourceTree = ""; }; - 97C0FFAB2D5DFF5700A36284 /* CameraUtilTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraUtilTests.m; sourceTree = ""; }; + 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPropertiesTests.swift; sourceTree = ""; }; + 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueUtilsTests.swift; sourceTree = ""; }; + 972CA9302D5A366C004B846F /* CameraExposureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraExposureTests.swift; sourceTree = ""; }; + 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvailableCamerasTests.swift; sourceTree = ""; }; + 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraFocusTests.swift; sourceTree = ""; }; + 977A25232D5A511600931E34 /* CameraPermissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPermissionTests.swift; sourceTree = ""; }; + 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = ""; }; + 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExceptionCatcher.m; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 97DB234B2D566D0600CEFE66 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; 97DB234C2D566D0700CEFE66 /* CameraPreviewPauseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPreviewPauseTests.swift; sourceTree = ""; }; 9DDC4CE84A8B378AE4A8CD9C /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A8F314CD1C64E9257EBC811D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; @@ -175,7 +174,6 @@ E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */, E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */, E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */, - 97C0FFAB2D5DFF5700A36284 /* CameraUtilTests.m */, E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */, E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */, 788A065927B0E02900533D74 /* StreamingTest.m */, @@ -552,7 +550,6 @@ 7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */, 7FCEDD362D43C2B900EA1CA8 /* MockCaptureDevice.m in Sources */, 7F8FD22C2D4D07DD001AF2C1 /* MockFlutterTextureRegistry.m in Sources */, - 97C0FFAC2D5DFF5700A36284 /* CameraUtilTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From ff8fd05bedca3fc6140c3f9af100dffc498bf0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 13:18:33 +0100 Subject: [PATCH 13/40] Migrate SavePhotoDelegateTests to Swift --- .../ios/Runner.xcodeproj/project.pbxproj | 35 ++- .../RunnerTests/FLTSavePhotoDelegateTests.m | 282 +++++++++--------- .../RunnerTests/RunnerTests-Bridging-Header.h | 1 + .../RunnerTests/SavePhotoDelegateTests.swift | 105 +++++++ .../FLTSavePhotoDelegate_Test.h | 3 +- 5 files changed, 268 insertions(+), 158 deletions(-) create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index 8beee77488f..a76b1180409 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -25,19 +25,20 @@ 7FD582122D579650003B1200 /* MockWritableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582112D579650003B1200 /* MockWritableData.m */; }; 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */; }; 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; - 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; - 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */; }; - 97BD4A0E2D5CC5AE00F857D5 /* CameraSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */; }; - 97BD4A102D5CE13500F857D5 /* CameraSessionPresetsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */; }; 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */; }; 972CA92D2D5A28C4004B846F /* QueueUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */; }; 972CA9312D5A366C004B846F /* CameraExposureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA9302D5A366C004B846F /* CameraExposureTests.swift */; }; 977A25202D5A439300931E34 /* AvailableCamerasTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */; }; 977A25222D5A49EC00931E34 /* CameraFocusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */; }; 977A25242D5A511600931E34 /* CameraPermissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25232D5A511600931E34 /* CameraPermissionTests.swift */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 979B3DFB2D5B6BC7009BDE1A /* ExceptionCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */; }; + 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; + 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; + 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */; }; + 97BD4A0E2D5CC5AE00F857D5 /* CameraSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */; }; + 97BD4A102D5CE13500F857D5 /* CameraSessionPresetsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */; }; + 97C0FFAE2D5E023200A36284 /* SavePhotoDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97C0FFAD2D5E023200A36284 /* SavePhotoDelegateTests.swift */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -106,29 +107,29 @@ 7FD582212D579ED9003B1200 /* MockCapturePhotoOutput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCapturePhotoOutput.h; sourceTree = ""; }; 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureDeviceFormat.m; sourceTree = ""; }; 7FD582362D57D989003B1200 /* MockCaptureDeviceFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureDeviceFormat.h; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; - 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCaptureSessionQueueRaceConditionTests.swift; sourceTree = ""; }; - 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraMethodChannelTests.swift; sourceTree = ""; }; - 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraOrientationTests.swift; sourceTree = ""; }; - 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSettingsTests.swift; sourceTree = ""; }; - 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSessionPresetsTests.swift; sourceTree = ""; }; 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPropertiesTests.swift; sourceTree = ""; }; 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueUtilsTests.swift; sourceTree = ""; }; 972CA9302D5A366C004B846F /* CameraExposureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraExposureTests.swift; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvailableCamerasTests.swift; sourceTree = ""; }; 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraFocusTests.swift; sourceTree = ""; }; 977A25232D5A511600931E34 /* CameraPermissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPermissionTests.swift; sourceTree = ""; }; 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = ""; }; 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExceptionCatcher.m; sourceTree = ""; }; + 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; + 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCaptureSessionQueueRaceConditionTests.swift; sourceTree = ""; }; + 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraMethodChannelTests.swift; sourceTree = ""; }; + 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraOrientationTests.swift; sourceTree = ""; }; + 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSettingsTests.swift; sourceTree = ""; }; + 97BD4A0F2D5CE13500F857D5 /* CameraSessionPresetsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSessionPresetsTests.swift; sourceTree = ""; }; + 97C0FFAD2D5E023200A36284 /* SavePhotoDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavePhotoDelegateTests.swift; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 97DB234B2D566D0600CEFE66 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; 97DB234C2D566D0700CEFE66 /* CameraPreviewPauseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPreviewPauseTests.swift; sourceTree = ""; }; 9DDC4CE84A8B378AE4A8CD9C /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A8F314CD1C64E9257EBC811D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; @@ -190,6 +191,7 @@ 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */, 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */, 977A25232D5A511600931E34 /* CameraPermissionTests.swift */, + 97C0FFAD2D5E023200A36284 /* SavePhotoDelegateTests.swift */, 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */, 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */, ); @@ -550,6 +552,7 @@ 7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */, 7FCEDD362D43C2B900EA1CA8 /* MockCaptureDevice.m in Sources */, 7F8FD22C2D4D07DD001AF2C1 /* MockFlutterTextureRegistry.m in Sources */, + 97C0FFAE2D5E023200A36284 /* SavePhotoDelegateTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m index 94354df696b..28c3196bed6 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m @@ -1,141 +1,141 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import AVFoundation; -@import XCTest; - -#import "MockWritableData.h" - -@interface FLTSavePhotoDelegateTests : XCTestCase - -@end - -@implementation FLTSavePhotoDelegateTests - -- (void)testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToCapture { - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"Must complete with error if failed to capture photo."]; - - NSError *captureError = [NSError errorWithDomain:@"test" code:0 userInfo:nil]; - dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); - FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] - initWithPath:@"test" - ioQueue:ioQueue - completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { - XCTAssertEqualObjects(captureError, error); - XCTAssertNil(path); - [completionExpectation fulfill]; - }]; - - [delegate handlePhotoCaptureResultWithError:captureError - photoDataProvider:^NSData * { - return nil; - }]; - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToWrite { - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"Must complete with error if failed to write file."]; - dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); - - NSError *ioError = [NSError errorWithDomain:@"IOError" - code:0 - userInfo:@{NSLocalizedDescriptionKey : @"Localized IO Error"}]; - FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] - initWithPath:@"test" - ioQueue:ioQueue - completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { - XCTAssertEqualObjects(ioError, error); - XCTAssertNil(path); - [completionExpectation fulfill]; - }]; - - MockWritableData *mockWritableData = [[MockWritableData alloc] init]; - mockWritableData.writeToFileStub = - ^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) { - *error = ioError; - return NO; - }; - - [delegate handlePhotoCaptureResultWithError:nil - photoDataProvider:^NSObject * { - return mockWritableData; - }]; - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testHandlePhotoCaptureResult_mustCompleteWithFilePathIfSuccessToWrite { - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"Must complete with file path if success to write file."]; - - dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); - NSString *filePath = @"test"; - FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] - initWithPath:filePath - ioQueue:ioQueue - completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { - XCTAssertNil(error); - XCTAssertEqualObjects(filePath, path); - [completionExpectation fulfill]; - }]; - - MockWritableData *mockWritableData = [[MockWritableData alloc] init]; - mockWritableData.writeToFileStub = - ^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) { - return YES; - }; - - [delegate handlePhotoCaptureResultWithError:nil - photoDataProvider:^NSObject * { - return mockWritableData; - }]; - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testHandlePhotoCaptureResult_bothProvideDataAndSaveFileMustRunOnIOQueue { - XCTestExpectation *dataProviderQueueExpectation = - [self expectationWithDescription:@"Data provider must run on io queue."]; - XCTestExpectation *writeFileQueueExpectation = - [self expectationWithDescription:@"File writing must run on io queue"]; - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"Must complete with file path if success to write file."]; - - dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); - const char *ioQueueSpecific = "io_queue_specific"; - dispatch_queue_set_specific(ioQueue, ioQueueSpecific, (void *)ioQueueSpecific, NULL); - - MockWritableData *mockWritableData = [[MockWritableData alloc] init]; - mockWritableData.writeToFileStub = - ^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) { - if (dispatch_get_specific(ioQueueSpecific)) { - [writeFileQueueExpectation fulfill]; - } - return YES; - }; - - NSString *filePath = @"test"; - FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] - initWithPath:filePath - ioQueue:ioQueue - completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { - [completionExpectation fulfill]; - }]; - - [delegate handlePhotoCaptureResultWithError:nil - photoDataProvider:^NSObject * { - if (dispatch_get_specific(ioQueueSpecific)) { - [dataProviderQueueExpectation fulfill]; - } - return mockWritableData; - }]; - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -@end +//// Copyright 2013 The Flutter Authors. All rights reserved. +//// Use of this source code is governed by a BSD-style license that can be +//// found in the LICENSE file. +// +//@import camera_avfoundation; +//#if __has_include() +//@import camera_avfoundation.Test; +//#endif +//@import AVFoundation; +//@import XCTest; +// +//#import "MockWritableData.h" +// +//@interface FLTSavePhotoDelegateTests : XCTestCase +// +//@end +// +//@implementation FLTSavePhotoDelegateTests +// +//- (void)testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToCapture { +// XCTestExpectation *completionExpectation = +// [self expectationWithDescription:@"Must complete with error if failed to capture photo."]; +// +// NSError *captureError = [NSError errorWithDomain:@"test" code:0 userInfo:nil]; +// dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); +// FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] +// initWithPath:@"test" +// ioQueue:ioQueue +// completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { +// XCTAssertEqualObjects(captureError, error); +// XCTAssertNil(path); +// [completionExpectation fulfill]; +// }]; +// +// [delegate handlePhotoCaptureResultWithError:captureError +// photoDataProvider:^NSData * { +// return nil; +// }]; +// [self waitForExpectationsWithTimeout:30 handler:nil]; +//} +// +//- (void)testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToWrite { +// XCTestExpectation *completionExpectation = +// [self expectationWithDescription:@"Must complete with error if failed to write file."]; +// dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); +// +// NSError *ioError = [NSError errorWithDomain:@"IOError" +// code:0 +// userInfo:@{NSLocalizedDescriptionKey : @"Localized IO Error"}]; +// FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] +// initWithPath:@"test" +// ioQueue:ioQueue +// completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { +// XCTAssertEqualObjects(ioError, error); +// XCTAssertNil(path); +// [completionExpectation fulfill]; +// }]; +// +// MockWritableData *mockWritableData = [[MockWritableData alloc] init]; +// mockWritableData.writeToFileStub = +// ^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) { +// *error = ioError; +// return NO; +// }; +// +// [delegate handlePhotoCaptureResultWithError:nil +// photoDataProvider:^NSObject * { +// return mockWritableData; +// }]; +// [self waitForExpectationsWithTimeout:30 handler:nil]; +//} +// +//- (void)testHandlePhotoCaptureResult_mustCompleteWithFilePathIfSuccessToWrite { +// XCTestExpectation *completionExpectation = +// [self expectationWithDescription:@"Must complete with file path if success to write file."]; +// +// dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); +// NSString *filePath = @"test"; +// FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] +// initWithPath:filePath +// ioQueue:ioQueue +// completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { +// XCTAssertNil(error); +// XCTAssertEqualObjects(filePath, path); +// [completionExpectation fulfill]; +// }]; +// +// MockWritableData *mockWritableData = [[MockWritableData alloc] init]; +// mockWritableData.writeToFileStub = +// ^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) { +// return YES; +// }; +// +// [delegate handlePhotoCaptureResultWithError:nil +// photoDataProvider:^NSObject * { +// return mockWritableData; +// }]; +// [self waitForExpectationsWithTimeout:30 handler:nil]; +//} +// +//- (void)testHandlePhotoCaptureResult_bothProvideDataAndSaveFileMustRunOnIOQueue { +// XCTestExpectation *dataProviderQueueExpectation = +// [self expectationWithDescription:@"Data provider must run on io queue."]; +// XCTestExpectation *writeFileQueueExpectation = +// [self expectationWithDescription:@"File writing must run on io queue"]; +// XCTestExpectation *completionExpectation = +// [self expectationWithDescription:@"Must complete with file path if success to write file."]; +// +// dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); +// const char *ioQueueSpecific = "io_queue_specific"; +// dispatch_queue_set_specific(ioQueue, ioQueueSpecific, (void *)ioQueueSpecific, NULL); +// +// MockWritableData *mockWritableData = [[MockWritableData alloc] init]; +// mockWritableData.writeToFileStub = +// ^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) { +// if (dispatch_get_specific(ioQueueSpecific)) { +// [writeFileQueueExpectation fulfill]; +// } +// return YES; +// }; +// +// NSString *filePath = @"test"; +// FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] +// initWithPath:filePath +// ioQueue:ioQueue +// completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { +// [completionExpectation fulfill]; +// }]; +// +// [delegate handlePhotoCaptureResultWithError:nil +// photoDataProvider:^NSObject * { +// if (dispatch_get_specific(ioQueueSpecific)) { +// [dataProviderQueueExpectation fulfill]; +// } +// return mockWritableData; +// }]; +// +// [self waitForExpectationsWithTimeout:30 handler:nil]; +//} +// +//@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index f39781492b2..aeccb54f3d9 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -19,6 +19,7 @@ #import "MockFlutterBinaryMessenger.h" #import "MockFlutterTextureRegistry.h" #import "MockGlobalEventApi.h" +#import "MockWritableData.h" // Utils. #import "CameraTestUtils.h" diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift new file mode 100644 index 00000000000..a3775058797 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift @@ -0,0 +1,105 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import AVFoundation +import XCTest + +@testable import camera_avfoundation + +final class SavePhotoDelegateTests: XCTestCase { + func testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToCapture() { + let completionExpectation = expectation( + description: "Must complete with error if failed to capture photo.") + + let captureError = NSError(domain: "test", code: 0, userInfo: nil) + let ioQueue = DispatchQueue(label: "test") + let delegate = FLTSavePhotoDelegate(path: "test", ioQueue: ioQueue) { path, error in + XCTAssertEqual(captureError, error as NSError?) + XCTAssertNil(path) + completionExpectation.fulfill() + } + + delegate.handlePhotoCaptureResult(error: captureError) { nil } + waitForExpectations(timeout: 30, handler: nil) + } + + func testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToWrite() { + let completionExpectation = expectation( + description: "Must complete with error if failed to write file.") + let ioQueue = DispatchQueue(label: "test") + + let ioError = NSError( + domain: "IOError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Localized IO Error"]) + let delegate = FLTSavePhotoDelegate(path: "test", ioQueue: ioQueue) { path, error in + XCTAssertEqual(ioError, error as NSError?) + XCTAssertNil(path) + completionExpectation.fulfill() + } + + let mockWritableData = MockWritableData() + mockWritableData.writeToFileStub = { path, options, error in + // TODO(FirentisTFW) Throw an error instead when migrating FLTWritableData to Swift + error?.pointee = ioError + return false + } + + delegate.handlePhotoCaptureResult(error: nil) { mockWritableData } + waitForExpectations(timeout: 30, handler: nil) + } + + func testHandlePhotoCaptureResult_mustCompleteWithFilePathIfSuccessToWrite() { + let completionExpectation = expectation( + description: "Must complete with file path if succeeds to write file.") + + let ioQueue = DispatchQueue(label: "test") + let filePath = "test" + let delegate = FLTSavePhotoDelegate(path: filePath, ioQueue: ioQueue) { path, error in + XCTAssertNil(error) + XCTAssertEqual(filePath, path) + completionExpectation.fulfill() + } + + let mockWritableData = MockWritableData() + mockWritableData.writeToFileStub = { path, options, error in + return true + } + + delegate.handlePhotoCaptureResult(error: nil) { mockWritableData } + waitForExpectations(timeout: 30, handler: nil) + } + + func testHandlePhotoCaptureResult_bothProvideDataAndSaveFileMustRunOnIOQueue() { + let dataProviderQueueExpectation = expectation( + description: "Data provider must run on io queue.") + let writeFileQueueExpectation = expectation(description: "File writing must run on io queue.") + let completionExpectation = expectation( + description: "Must complete with file path if success to write file.") + + let ioQueue = DispatchQueue(label: "test") + let ioQueueSpecific = DispatchSpecificKey() + ioQueue.setSpecific(key: ioQueueSpecific, value: ()) + + let mockWritableData = MockWritableData() + mockWritableData.writeToFileStub = { path, options, error in + if DispatchQueue.getSpecific(key: ioQueueSpecific) != nil { + writeFileQueueExpectation.fulfill() + } + return true + } + + let filePath = "test" + let delegate = FLTSavePhotoDelegate(path: filePath, ioQueue: ioQueue) { path, error in + completionExpectation.fulfill() + } + + delegate.handlePhotoCaptureResult(error: nil) { + if DispatchQueue.getSpecific(key: ioQueueSpecific) != nil { + dataProviderQueueExpectation.fulfill() + } + return mockWritableData + } + + waitForExpectations(timeout: 30, handler: nil) + } +} diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTSavePhotoDelegate_Test.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTSavePhotoDelegate_Test.h index 7c242e53f58..5d9767e0005 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTSavePhotoDelegate_Test.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTSavePhotoDelegate_Test.h @@ -22,5 +22,6 @@ /// @param error the capture error. /// @param photoDataProvider a closure that provides photo data. - (void)handlePhotoCaptureResultWithError:(NSError *)error - photoDataProvider:(NSObject * (^)(void))photoDataProvider; + photoDataProvider:(NSObject * (^)(void))photoDataProvider + NS_SWIFT_NAME(handlePhotoCaptureResult(error:photoDataProvider:)); @end From 974e72890162dd602729db117ab3beecf16ab99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 14:02:31 +0100 Subject: [PATCH 14/40] Clean up - Swift tests migration --- .../ios/Runner.xcodeproj/project.pbxproj | 6 +- ...aptureSessionQueueRaceConditionTests.swift | 3 +- .../CameraMethodChannelTests.swift | 8 +- .../RunnerTests/CameraOrientationTests.swift | 6 +- .../ios/RunnerTests/CameraSettingsTests.swift | 2 +- .../RunnerTests/FLTSavePhotoDelegateTests.m | 141 ------------------ .../RunnerTests/RunnerTests-Bridging-Header.h | 2 +- .../RunnerTests/SavePhotoDelegateTests.swift | 7 +- .../camera_avfoundation/FLTWritableData.m | 1 + .../include/camera_avfoundation/FLTCam.h | 3 +- 10 files changed, 12 insertions(+), 167 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index a76b1180409..2788c13f1f6 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -44,7 +44,6 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97DB234D2D566D0700CEFE66 /* CameraPreviewPauseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DB234C2D566D0700CEFE66 /* CameraPreviewPauseTests.swift */; }; - E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */; }; E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */; }; E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */; }; E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */; }; @@ -134,7 +133,6 @@ 9DDC4CE84A8B378AE4A8CD9C /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A8F314CD1C64E9257EBC811D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; B61D98BBC8FB276D1C4A7BB2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTSavePhotoDelegateTests.m; sourceTree = ""; }; E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCamPhotoCaptureTests.m; sourceTree = ""; }; E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTCamSampleBufferTests.m; sourceTree = ""; }; E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeEventChannelTests.m; sourceTree = ""; }; @@ -172,14 +170,13 @@ 7F29EB3F2D281C6D00740257 /* Mocks */, 03BB766C2665316900CE5A93 /* Info.plist */, E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */, - E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */, E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */, E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */, E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */, E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */, 788A065927B0E02900533D74 /* StreamingTest.m */, - 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */, 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */, + 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */, 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */, 979B3E012D5BA48F009BDE1A /* CameraOrientationTests.swift */, 97BD4A0D2D5CC5AE00F857D5 /* CameraSettingsTests.swift */, @@ -533,7 +530,6 @@ 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */, 7F29EB222D269ED500740257 /* MockEventChannel.m in Sources */, 7F8FD22F2D4D0B88001AF2C1 /* MockFlutterBinaryMessenger.m in Sources */, - E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */, 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */, E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */, 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift index 6da49ca1257..1e0b6dee2c2 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift @@ -7,7 +7,7 @@ import XCTest @testable import camera_avfoundation final class CameraCaptureSessionQueueRaceConditionTests: XCTestCase { - func createCameraPlugin() -> CameraPlugin { + private func createCameraPlugin() -> CameraPlugin { let captureDevice = MockCaptureDevice() return CameraPlugin( @@ -23,7 +23,6 @@ final class CameraCaptureSessionQueueRaceConditionTests: XCTestCase { func testFixForCaptureSessionQueueNullPointerCrashDueToRaceCondition() { let cameraPlugin = createCameraPlugin() - let disposeExpectation = expectation(description: "dispose's result block must be called") let createExpectation = expectation(description: "create's result block must be called") diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.swift index eda19a30ddb..5ad12652875 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.swift @@ -8,7 +8,7 @@ import XCTest @testable import camera_avfoundation final class CameraMethodChannelTests: XCTestCase { - func createCameraPlugin(with session: MockCaptureSession) -> CameraPlugin { + private func createCameraPlugin(with session: MockCaptureSession) -> CameraPlugin { return CameraPlugin( registry: MockFlutterTextureRegistry(), messenger: MockFlutterBinaryMessenger(), @@ -23,9 +23,7 @@ final class CameraMethodChannelTests: XCTestCase { func testCreate_ShouldCallResultOnMainThread() { let avCaptureSessionMock = MockCaptureSession() avCaptureSessionMock.canSetSessionPreset = true - let camera = createCameraPlugin(with: avCaptureSessionMock) - let expectation = self.expectation(description: "Result finished") var resultValue: NSNumber? @@ -44,17 +42,15 @@ final class CameraMethodChannelTests: XCTestCase { } waitForExpectations(timeout: 30, handler: nil) - XCTAssertNotNil(resultValue) } func testDisposeShouldDeallocCamera() { let avCaptureSessionMock = MockCaptureSession() avCaptureSessionMock.canSetSessionPreset = true - let camera = createCameraPlugin(with: avCaptureSessionMock) - let createExpectation = self.expectation(description: "create's result block must be called") + camera.createCameraOnSessionQueue( withName: "acamera", settings: FCPPlatformMediaSettings.make( diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift index 5c8e5bbd940..6a0dcea4be3 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift @@ -14,10 +14,6 @@ private final class MockCamera: FLTCam { override func setDeviceOrientation(_ orientation: UIDeviceOrientation) { setDeviceOrientationStub?(orientation) } - - func setCaptureDevice(_ device: FLTCaptureDevice) { - self.captureDevice = device - } } private final class MockUIDevice: UIDevice { @@ -117,7 +113,7 @@ final class CameraOrientationTests: XCTestCase { waitForExpectations(timeout: 30, handler: nil) } - func testOrientationChanged_noRetainCycle() { + func testOrientationChangedNoRetainCycle() { let captureSessionQueue = DispatchQueue(label: "capture_session_queue") weak var weakPlugin: CameraPlugin? weak var weakDevice: MockCaptureDevice! = mockDevice diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift index cd8f99a0ebe..caaca91c11c 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift @@ -97,7 +97,7 @@ private final class TestMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { } override func addInput(_ writerInput: AVAssetWriterInput, to writer: AVAssetWriter) { - // No-op + // No-op. } override func recommendedVideoSettingsForAssetWriter( diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m deleted file mode 100644 index 28c3196bed6..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m +++ /dev/null @@ -1,141 +0,0 @@ -//// Copyright 2013 The Flutter Authors. All rights reserved. -//// Use of this source code is governed by a BSD-style license that can be -//// found in the LICENSE file. -// -//@import camera_avfoundation; -//#if __has_include() -//@import camera_avfoundation.Test; -//#endif -//@import AVFoundation; -//@import XCTest; -// -//#import "MockWritableData.h" -// -//@interface FLTSavePhotoDelegateTests : XCTestCase -// -//@end -// -//@implementation FLTSavePhotoDelegateTests -// -//- (void)testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToCapture { -// XCTestExpectation *completionExpectation = -// [self expectationWithDescription:@"Must complete with error if failed to capture photo."]; -// -// NSError *captureError = [NSError errorWithDomain:@"test" code:0 userInfo:nil]; -// dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); -// FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] -// initWithPath:@"test" -// ioQueue:ioQueue -// completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { -// XCTAssertEqualObjects(captureError, error); -// XCTAssertNil(path); -// [completionExpectation fulfill]; -// }]; -// -// [delegate handlePhotoCaptureResultWithError:captureError -// photoDataProvider:^NSData * { -// return nil; -// }]; -// [self waitForExpectationsWithTimeout:30 handler:nil]; -//} -// -//- (void)testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToWrite { -// XCTestExpectation *completionExpectation = -// [self expectationWithDescription:@"Must complete with error if failed to write file."]; -// dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); -// -// NSError *ioError = [NSError errorWithDomain:@"IOError" -// code:0 -// userInfo:@{NSLocalizedDescriptionKey : @"Localized IO Error"}]; -// FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] -// initWithPath:@"test" -// ioQueue:ioQueue -// completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { -// XCTAssertEqualObjects(ioError, error); -// XCTAssertNil(path); -// [completionExpectation fulfill]; -// }]; -// -// MockWritableData *mockWritableData = [[MockWritableData alloc] init]; -// mockWritableData.writeToFileStub = -// ^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) { -// *error = ioError; -// return NO; -// }; -// -// [delegate handlePhotoCaptureResultWithError:nil -// photoDataProvider:^NSObject * { -// return mockWritableData; -// }]; -// [self waitForExpectationsWithTimeout:30 handler:nil]; -//} -// -//- (void)testHandlePhotoCaptureResult_mustCompleteWithFilePathIfSuccessToWrite { -// XCTestExpectation *completionExpectation = -// [self expectationWithDescription:@"Must complete with file path if success to write file."]; -// -// dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); -// NSString *filePath = @"test"; -// FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] -// initWithPath:filePath -// ioQueue:ioQueue -// completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { -// XCTAssertNil(error); -// XCTAssertEqualObjects(filePath, path); -// [completionExpectation fulfill]; -// }]; -// -// MockWritableData *mockWritableData = [[MockWritableData alloc] init]; -// mockWritableData.writeToFileStub = -// ^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) { -// return YES; -// }; -// -// [delegate handlePhotoCaptureResultWithError:nil -// photoDataProvider:^NSObject * { -// return mockWritableData; -// }]; -// [self waitForExpectationsWithTimeout:30 handler:nil]; -//} -// -//- (void)testHandlePhotoCaptureResult_bothProvideDataAndSaveFileMustRunOnIOQueue { -// XCTestExpectation *dataProviderQueueExpectation = -// [self expectationWithDescription:@"Data provider must run on io queue."]; -// XCTestExpectation *writeFileQueueExpectation = -// [self expectationWithDescription:@"File writing must run on io queue"]; -// XCTestExpectation *completionExpectation = -// [self expectationWithDescription:@"Must complete with file path if success to write file."]; -// -// dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); -// const char *ioQueueSpecific = "io_queue_specific"; -// dispatch_queue_set_specific(ioQueue, ioQueueSpecific, (void *)ioQueueSpecific, NULL); -// -// MockWritableData *mockWritableData = [[MockWritableData alloc] init]; -// mockWritableData.writeToFileStub = -// ^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) { -// if (dispatch_get_specific(ioQueueSpecific)) { -// [writeFileQueueExpectation fulfill]; -// } -// return YES; -// }; -// -// NSString *filePath = @"test"; -// FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] -// initWithPath:filePath -// ioQueue:ioQueue -// completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { -// [completionExpectation fulfill]; -// }]; -// -// [delegate handlePhotoCaptureResultWithError:nil -// photoDataProvider:^NSObject * { -// if (dispatch_get_specific(ioQueueSpecific)) { -// [dataProviderQueueExpectation fulfill]; -// } -// return mockWritableData; -// }]; -// -// [self waitForExpectationsWithTimeout:30 handler:nil]; -//} -// -//@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index aeccb54f3d9..c4a307e3f85 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -5,7 +5,7 @@ // Sources. #import "camera_avfoundation/CameraPlugin.h" #import "camera_avfoundation/CameraPlugin_Test.h" -#import "camera_avfoundation/FLTCam.h" +#import "camera_avfoundation/FLTCam_Test.h" #import "camera_avfoundation/FLTCamConfiguration.h" #import "camera_avfoundation/FLTThreadSafeEventChannel.h" diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift index a3775058797..02a927557aa 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift @@ -11,7 +11,6 @@ final class SavePhotoDelegateTests: XCTestCase { func testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToCapture() { let completionExpectation = expectation( description: "Must complete with error if failed to capture photo.") - let captureError = NSError(domain: "test", code: 0, userInfo: nil) let ioQueue = DispatchQueue(label: "test") let delegate = FLTSavePhotoDelegate(path: "test", ioQueue: ioQueue) { path, error in @@ -21,6 +20,7 @@ final class SavePhotoDelegateTests: XCTestCase { } delegate.handlePhotoCaptureResult(error: captureError) { nil } + waitForExpectations(timeout: 30, handler: nil) } @@ -28,7 +28,6 @@ final class SavePhotoDelegateTests: XCTestCase { let completionExpectation = expectation( description: "Must complete with error if failed to write file.") let ioQueue = DispatchQueue(label: "test") - let ioError = NSError( domain: "IOError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Localized IO Error"]) let delegate = FLTSavePhotoDelegate(path: "test", ioQueue: ioQueue) { path, error in @@ -45,13 +44,13 @@ final class SavePhotoDelegateTests: XCTestCase { } delegate.handlePhotoCaptureResult(error: nil) { mockWritableData } + waitForExpectations(timeout: 30, handler: nil) } func testHandlePhotoCaptureResult_mustCompleteWithFilePathIfSuccessToWrite() { let completionExpectation = expectation( description: "Must complete with file path if succeeds to write file.") - let ioQueue = DispatchQueue(label: "test") let filePath = "test" let delegate = FLTSavePhotoDelegate(path: filePath, ioQueue: ioQueue) { path, error in @@ -66,6 +65,7 @@ final class SavePhotoDelegateTests: XCTestCase { } delegate.handlePhotoCaptureResult(error: nil) { mockWritableData } + waitForExpectations(timeout: 30, handler: nil) } @@ -75,7 +75,6 @@ final class SavePhotoDelegateTests: XCTestCase { let writeFileQueueExpectation = expectation(description: "File writing must run on io queue.") let completionExpectation = expectation( description: "Must complete with file path if success to write file.") - let ioQueue = DispatchQueue(label: "test") let ioQueueSpecific = DispatchSpecificKey() ioQueue.setSpecific(key: ioQueueSpecific, value: ()) diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTWritableData.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTWritableData.m index a6d3d3bca96..15220b5477f 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTWritableData.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTWritableData.m @@ -21,3 +21,4 @@ - (BOOL)writeToFile:(NSString *)path } @end + diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h index edd38d089e0..e7dd92a7b83 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h @@ -18,8 +18,7 @@ NS_ASSUME_NONNULL_BEGIN /// A class that manages camera's state and performs camera operations. @interface FLTCam : NSObject -// FIXME It was readonly before -@property(nonatomic) NSObject *captureDevice; +@property(readonly, nonatomic) NSObject *captureDevice; @property(readonly, nonatomic) CGSize previewSize; @property(assign, nonatomic) BOOL isPreviewPaused; @property(nonatomic, copy) void (^onFrameAvailable)(void); From 612c425216e6c498d92e9e746fd8725030ace3e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 16:12:04 +0100 Subject: [PATCH 15/40] Sort imports --- .../example/ios/RunnerTests/RunnerTests-Bridging-Header.h | 2 +- .../Sources/camera_avfoundation/FLTWritableData.m | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index c4a307e3f85..57378ab3256 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -5,8 +5,8 @@ // Sources. #import "camera_avfoundation/CameraPlugin.h" #import "camera_avfoundation/CameraPlugin_Test.h" -#import "camera_avfoundation/FLTCam_Test.h" #import "camera_avfoundation/FLTCamConfiguration.h" +#import "camera_avfoundation/FLTCam_Test.h" #import "camera_avfoundation/FLTThreadSafeEventChannel.h" // Mocks, protocols. diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTWritableData.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTWritableData.m index 15220b5477f..a6d3d3bca96 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTWritableData.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTWritableData.m @@ -21,4 +21,3 @@ - (BOOL)writeToFile:(NSString *)path } @end - From 29870d575ae86b1d691ed46ee2fa05fcbfcdd95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 20:50:08 +0100 Subject: [PATCH 16/40] Clean up code --- .../CameraCaptureSessionQueueRaceConditionTests.swift | 4 +--- .../example/ios/RunnerTests/CameraOrientationTests.swift | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift index 1e0b6dee2c2..bf0f7d300c7 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.swift @@ -8,14 +8,12 @@ import XCTest final class CameraCaptureSessionQueueRaceConditionTests: XCTestCase { private func createCameraPlugin() -> CameraPlugin { - let captureDevice = MockCaptureDevice() - return CameraPlugin( registry: MockFlutterTextureRegistry(), messenger: MockFlutterBinaryMessenger(), globalAPI: MockGlobalEventApi(), deviceDiscoverer: MockCameraDeviceDiscoverer(), - deviceFactory: { _ in captureDevice }, + deviceFactory: { _ in MockCaptureDevice() }, captureSessionFactory: { MockCaptureSession() }, captureDeviceInputFactory: MockCaptureDeviceInputFactory() ) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift index 6a0dcea4be3..ca304ebcd4b 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift @@ -97,8 +97,7 @@ final class CameraOrientationTests: XCTestCase { let plugin = CameraPlugin( registry: MockFlutterTextureRegistry(), - messenger: MockFlutterBinaryMessenger() - ) + messenger: MockFlutterBinaryMessenger()) let captureSessionQueueSpecific = DispatchSpecificKey() plugin.captureSessionQueue.setSpecific(key: captureSessionQueueSpecific, value: ()) plugin.camera = mockCamera From 828f73e807aa2a25ac566936dbd1f0919f036a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 21:18:21 +0100 Subject: [PATCH 17/40] Remove shared state from CameraOrientationTests --- .../RunnerTests/CameraOrientationTests.swift | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift index ca304ebcd4b..c676a378af2 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift @@ -25,28 +25,26 @@ private final class MockUIDevice: UIDevice { } final class CameraOrientationTests: XCTestCase { - private var mockCamera: MockCamera! - private var mockDevice: MockCaptureDevice! - private var mockEventAPI: MockGlobalEventApi! - private var mockDeviceDiscoverer: MockCameraDeviceDiscoverer! - private var cameraPlugin: CameraPlugin! - - override func setUp() { - mockDevice = MockCaptureDevice() - mockCamera = MockCamera() - mockEventAPI = MockGlobalEventApi() - mockDeviceDiscoverer = MockCameraDeviceDiscoverer() - - cameraPlugin = CameraPlugin( + private func createCameraPlugin() -> ( + CameraPlugin, MockCamera, MockGlobalEventApi, MockCaptureDevice, MockCameraDeviceDiscoverer + ) { + let mockDevice = MockCaptureDevice() + let mockCamera = MockCamera() + let mockEventAPI = MockGlobalEventApi() + let mockDeviceDiscoverer = MockCameraDeviceDiscoverer() + + let cameraPlugin = CameraPlugin( registry: MockFlutterTextureRegistry(), messenger: MockFlutterBinaryMessenger(), globalAPI: mockEventAPI, deviceDiscoverer: mockDeviceDiscoverer, - deviceFactory: { _ in self.mockDevice }, + deviceFactory: { _ in mockDevice }, captureSessionFactory: { MockCaptureSession() }, captureDeviceInputFactory: MockCaptureDeviceInputFactory() ) cameraPlugin.camera = mockCamera + + return (cameraPlugin, mockCamera, mockEventAPI, mockDevice, mockDeviceDiscoverer) } private func waitForRoundTrip(with queue: DispatchQueue) { @@ -71,6 +69,8 @@ final class CameraOrientationTests: XCTestCase { } func testOrientationNotifications() { + let (cameraPlugin, _, mockEventAPI, _, _) = createCameraPlugin() + sendOrientation(.portraitUpsideDown, to: cameraPlugin) XCTAssertEqual(mockEventAPI.lastOrientation, .portraitDown) sendOrientation(.portrait, to: cameraPlugin) @@ -82,11 +82,13 @@ final class CameraOrientationTests: XCTestCase { } func testOrientationNotificationsNotCalledForFaceUp() { + let (cameraPlugin, _, mockEventAPI, _, _) = createCameraPlugin() sendOrientation(.faceUp, to: cameraPlugin) XCTAssertFalse(mockEventAPI.deviceOrientationChangedCalled) } func testOrientationNotificationsNotCalledForFaceDown() { + let (cameraPlugin, _, mockEventAPI, _, _) = createCameraPlugin() sendOrientation(.faceDown, to: cameraPlugin) XCTAssertFalse(mockEventAPI.deviceOrientationChangedCalled) } @@ -94,13 +96,11 @@ final class CameraOrientationTests: XCTestCase { func testOrientationUpdateMustBeOnCaptureSessionQueue() { let queueExpectation = expectation( description: "Orientation update must happen on the capture session queue") - - let plugin = CameraPlugin( - registry: MockFlutterTextureRegistry(), - messenger: MockFlutterBinaryMessenger()) + let (cameraPlugin, mockCamera, _, _, _) = createCameraPlugin() let captureSessionQueueSpecific = DispatchSpecificKey() - plugin.captureSessionQueue.setSpecific(key: captureSessionQueueSpecific, value: ()) - plugin.camera = mockCamera + cameraPlugin.captureSessionQueue.setSpecific( + key: captureSessionQueueSpecific, + value: ()) mockCamera.setDeviceOrientationStub = { orientation in if DispatchQueue.getSpecific(key: captureSessionQueueSpecific) != nil { @@ -108,30 +108,31 @@ final class CameraOrientationTests: XCTestCase { } } - plugin.orientationChanged(createMockNotification(for: .landscapeLeft)) + 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: MockCaptureDevice! = mockDevice + weak var weakDevice = mockDevice autoreleasepool { - let plugin = CameraPlugin( + let cameraPlugin = CameraPlugin( registry: MockFlutterTextureRegistry(), messenger: MockFlutterBinaryMessenger(), globalAPI: mockEventAPI, deviceDiscoverer: mockDeviceDiscoverer, - deviceFactory: { _ in weakDevice }, + deviceFactory: { _ in weakDevice! }, captureSessionFactory: { MockCaptureSession() }, captureDeviceInputFactory: MockCaptureDeviceInputFactory() ) - weakPlugin = plugin - plugin.captureSessionQueue = captureSessionQueue - plugin.camera = mockCamera + weakPlugin = cameraPlugin + cameraPlugin.captureSessionQueue = captureSessionQueue + cameraPlugin.camera = mockCamera - plugin.orientationChanged(createMockNotification(for: .landscapeLeft)) + cameraPlugin.orientationChanged(createMockNotification(for: .landscapeLeft)) } // Sanity check. @@ -141,6 +142,7 @@ final class CameraOrientationTests: XCTestCase { XCTAssertNil(weakPlugin) cameraDeallocatedExpectation.fulfill() } + // Awaiting expectation is needed. The test is flaky when checking for null right away. waitForExpectations(timeout: 1, handler: nil) var setDeviceOrientationCalled = false From 4b74842f1cff92cb323ded0b0eb03e5569e882bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 21:31:27 +0100 Subject: [PATCH 18/40] Bump version --- packages/camera/camera_avfoundation/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 8ba6e0d0637..2dcc784d631 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.18+6 +version: 0.9.18+7 environment: sdk: ^3.4.0 From 3b2b4a944ec1edbb7c150e736856fd68f34b5b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 21:48:12 +0100 Subject: [PATCH 19/40] Adjust code to the new API after merge --- .../example/ios/RunnerTests/CameraSettingsTests.swift | 10 +++++----- .../ios/RunnerTests/RunnerTests-Bridging-Header.h | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift index caaca91c11c..cd449b39d69 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift @@ -67,16 +67,16 @@ private final class TestMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { } override func assetWriterAudioInput(withOutputSettings outputSettings: [String: Any]?) - -> AVAssetWriterInput + -> FLTAssetWriterInput { if let bitrate = outputSettings?[AVEncoderBitRateKey] as? Int, bitrate == testAudioBitrate { audioSettingsExpectation.fulfill() } - return AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings) + return MockAssetWriterInput() } override func assetWriterVideoInput(withOutputSettings outputSettings: [String: Any]?) - -> AVAssetWriterInput + -> FLTAssetWriterInput { if let compressionProperties = outputSettings?[AVVideoCompressionPropertiesKey] as? [String: Any], @@ -93,10 +93,10 @@ private final class TestMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { outputSettingsWithRequiredKeys[AVVideoWidthKey] = 1280 outputSettingsWithRequiredKeys[AVVideoHeightKey] = 720 - return AVAssetWriterInput(mediaType: .video, outputSettings: outputSettingsWithRequiredKeys) + return MockAssetWriterInput() } - override func addInput(_ writerInput: AVAssetWriterInput, to writer: AVAssetWriter) { + override func addInput(_ writerInput: FLTAssetWriterInput, to writer: FLTAssetWriter) { // No-op. } diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index 57378ab3256..4e0309c4909 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -10,6 +10,7 @@ #import "camera_avfoundation/FLTThreadSafeEventChannel.h" // Mocks, protocols. +#import "MockAssetWriter.h" #import "MockCameraDeviceDiscoverer.h" #import "MockCaptureDevice.h" #import "MockCaptureDeviceFormat.h" From 75a58a0c04dd4b851564f9fbc6d817fa4a36e42f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 13 Feb 2025 21:51:51 +0100 Subject: [PATCH 20/40] Fix a comment --- .../example/ios/RunnerTests/CameraOrientationTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift index c676a378af2..d6198f35d88 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift @@ -142,7 +142,7 @@ final class CameraOrientationTests: XCTestCase { XCTAssertNil(weakPlugin) cameraDeallocatedExpectation.fulfill() } - // Awaiting expectation is needed. The test is flaky when checking for null right away. + // Awaiting expectation is needed. The test is flaky when checking for nil right away. waitForExpectations(timeout: 1, handler: nil) var setDeviceOrientationCalled = false From 4237804ca788f863cfdefd2fa259c75d9f8b90cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Mon, 17 Feb 2025 14:45:23 +0100 Subject: [PATCH 21/40] Adjust code after merge --- .../example/ios/RunnerTests/CameraSessionPresetsTests.swift | 2 +- .../example/ios/RunnerTests/CameraSettingsTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.swift index 3736f6aacf4..4be4db51554 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.swift @@ -24,7 +24,7 @@ final class CameraSessionPresetsTests: XCTestCase { let captureFormatMock = MockCaptureDeviceFormat() let captureDeviceMock = MockCaptureDevice() captureDeviceMock.formats = [captureFormatMock] - captureDeviceMock.setActiveFormat(captureFormatMock) + captureDeviceMock.activeFormat = captureFormatMock captureDeviceMock.lockForConfigurationStub = { error in lockForConfigurationExpectation.fulfill() return true diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift index cd449b39d69..eb3d06dff49 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift @@ -196,7 +196,7 @@ final class CameraSettingsTests: XCTestCase { configuration.mediaSettings = settings let camera = FLTCreateCamWithConfiguration(configuration) - let range = camera.captureDevice.activeFormat().videoSupportedFrameRateRanges[0] + let range = camera.captureDevice.activeFormat.videoSupportedFrameRateRanges[0] XCTAssertLessThanOrEqual(range.minFrameRate, 60) XCTAssertGreaterThanOrEqual(range.maxFrameRate, 60) } From a457cb204abc4af3aa96da93175acf3fb5fd9708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Fri, 14 Feb 2025 11:46:15 +0100 Subject: [PATCH 22/40] Migrate ThreadSafeEventChannelTests to Swift --- .../ios/Runner.xcodeproj/project.pbxproj | 8 +- .../RunnerTests/ThreadSafeEventChannelTests.m | 92 ------------------- .../ThreadSafeEventChannelTests.swift | 83 +++++++++++++++++ 3 files changed, 89 insertions(+), 94 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.m create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index 8d0c4d0b0d2..a0d2b33dcee 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 977A25202D5A439300931E34 /* AvailableCamerasTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */; }; 977A25222D5A49EC00931E34 /* CameraFocusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */; }; 977A25242D5A511600931E34 /* CameraPermissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25232D5A511600931E34 /* CameraPermissionTests.swift */; }; + 977CAC9F2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 979B3DFB2D5B6BC7009BDE1A /* ExceptionCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */; }; 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; @@ -47,7 +48,6 @@ 97DB234D2D566D0700CEFE66 /* CameraPreviewPauseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DB234C2D566D0700CEFE66 /* CameraPreviewPauseTests.swift */; }; E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */; }; E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */; }; - E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */; }; E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */; }; /* End PBXBuildFile section */ @@ -121,6 +121,7 @@ 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvailableCamerasTests.swift; sourceTree = ""; }; 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraFocusTests.swift; sourceTree = ""; }; 977A25232D5A511600931E34 /* CameraPermissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPermissionTests.swift; sourceTree = ""; }; + 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSafeEventChannelTests.swift; sourceTree = ""; }; 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = ""; }; 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExceptionCatcher.m; sourceTree = ""; }; 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; @@ -142,7 +143,6 @@ B61D98BBC8FB276D1C4A7BB2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCamPhotoCaptureTests.m; sourceTree = ""; }; E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTCamSampleBufferTests.m; sourceTree = ""; }; - E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeEventChannelTests.m; sourceTree = ""; }; E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraTestUtils.h; sourceTree = ""; }; E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraTestUtils.m; sourceTree = ""; }; E67C6DBF6478BE708993169F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; @@ -176,6 +176,8 @@ 7F29EB3F2D281C6D00740257 /* Mocks */, 03BB766C2665316900CE5A93 /* Info.plist */, E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */, + 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */, + E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */, E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */, E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */, E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */, @@ -197,6 +199,7 @@ 97C0FFAD2D5E023200A36284 /* SavePhotoDelegateTests.swift */, 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */, 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */, + 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -557,6 +560,7 @@ 7F29EB412D281C7E00740257 /* MockCaptureSession.m in Sources */, 7FD582122D579650003B1200 /* MockWritableData.m in Sources */, 7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */, + 977CAC9F2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift in Sources */, 7FCEDD362D43C2B900EA1CA8 /* MockCaptureDevice.m in Sources */, 7F8FD22C2D4D07DD001AF2C1 /* MockFlutterTextureRegistry.m in Sources */, 97C0FFAE2D5E023200A36284 /* SavePhotoDelegateTests.swift in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.m deleted file mode 100644 index 7c845892d55..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.m +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import XCTest; - -#import "MockEventChannel.h" - -@interface ThreadSafeEventChannelTests : XCTestCase - -@end - -@implementation ThreadSafeEventChannelTests - -- (void)testSetStreamHandler_shouldStayOnMainThreadIfCalledFromMainThread { - MockEventChannel *mockEventChannel = [[MockEventChannel alloc] init]; - FLTThreadSafeEventChannel *threadSafeEventChannel = - [[FLTThreadSafeEventChannel alloc] initWithEventChannel:mockEventChannel]; - - XCTestExpectation *mainThreadExpectation = - [self expectationWithDescription:@"setStreamHandler must be called on the main thread"]; - XCTestExpectation *mainThreadCompletionExpectation = - [self expectationWithDescription: - @"setStreamHandler's completion block must be called on the main thread"]; - - [mockEventChannel setSetStreamHandlerStub:^(NSObject *handler) { - if (NSThread.isMainThread) { - [mainThreadExpectation fulfill]; - } - }]; - - [threadSafeEventChannel setStreamHandler:nil - completion:^{ - if (NSThread.isMainThread) { - [mainThreadCompletionExpectation fulfill]; - } - }]; - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testSetStreamHandler_shouldDispatchToMainThreadIfCalledFromBackgroundThread { - MockEventChannel *mockEventChannel = [[MockEventChannel alloc] init]; - FLTThreadSafeEventChannel *threadSafeEventChannel = - [[FLTThreadSafeEventChannel alloc] initWithEventChannel:mockEventChannel]; - - XCTestExpectation *mainThreadExpectation = - [self expectationWithDescription:@"setStreamHandler must be called on the main thread"]; - XCTestExpectation *mainThreadCompletionExpectation = - [self expectationWithDescription: - @"setStreamHandler's completion block must be called on the main thread"]; - - [mockEventChannel setSetStreamHandlerStub:^(NSObject *handler) { - if (NSThread.isMainThread) { - [mainThreadExpectation fulfill]; - } - }]; - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [threadSafeEventChannel setStreamHandler:nil - completion:^{ - if (NSThread.isMainThread) { - [mainThreadCompletionExpectation fulfill]; - } - }]; - }); - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testEventChannel_shouldBeKeptAliveWhenDispatchingBackToMainThread { - MockEventChannel *mockEventChannel = [[MockEventChannel alloc] init]; - - XCTestExpectation *expectation = - [self expectationWithDescription:@"Completion should be called."]; - - dispatch_async(dispatch_queue_create("test", NULL), ^{ - FLTThreadSafeEventChannel *channel = - [[FLTThreadSafeEventChannel alloc] initWithEventChannel:mockEventChannel]; - - [channel setStreamHandler:nil - completion:^{ - [expectation fulfill]; - }]; - }); - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift new file mode 100644 index 00000000000..8728405db7c --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift @@ -0,0 +1,83 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import XCTest + +@testable import camera_avfoundation + +private final class MockEventChannel: NSObject, FLTEventChannel { + var setStreamHandlerStub: ((FlutterStreamHandler?) -> Void)? + + func setStreamHandler(_ handler: (FlutterStreamHandler & NSObjectProtocol)?) { + setStreamHandlerStub?(handler) + } +} + +final class ThreadSafeEventChannelTests: XCTestCase { + func testSetStreamHandler_shouldStayOnMainThreadIfCalledFromMainThread() { + let mockEventChannel = MockEventChannel() + let threadSafeEventChannel = FLTThreadSafeEventChannel(eventChannel: mockEventChannel) + + let mainThreadExpectation = expectation( + description: "setStreamHandler must be called on the main thread") + let mainThreadCompletionExpectation = expectation( + description: "setStreamHandler's completion block must be called on the main thread") + + mockEventChannel.setStreamHandlerStub = { handler in + if Thread.isMainThread { + mainThreadExpectation.fulfill() + } + } + + threadSafeEventChannel.setStreamHandler(nil) { + if Thread.isMainThread { + mainThreadCompletionExpectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testSetStreamHandler_shouldDispatchToMainThreadIfCalledFromBackgroundThread() { + let mockEventChannel = MockEventChannel() + let threadSafeEventChannel = FLTThreadSafeEventChannel(eventChannel: mockEventChannel) + + let mainThreadExpectation = expectation( + description: "setStreamHandler must be called on the main thread") + let mainThreadCompletionExpectation = expectation( + description: "setStreamHandler's completion block must be called on the main thread") + + mockEventChannel.setStreamHandlerStub = { handler in + if Thread.isMainThread { + mainThreadExpectation.fulfill() + } + } + + DispatchQueue.global(qos: .default).async { + threadSafeEventChannel.setStreamHandler(nil) { + if Thread.isMainThread { + mainThreadCompletionExpectation.fulfill() + } + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testEventChannel_shouldBeKeptAliveWhenDispatchingBackToMainThread() { + let mockEventChannel = MockEventChannel() + + let expectation = self.expectation(description: "Completion should be called.") + + DispatchQueue(label: "test").async { + let channel = FLTThreadSafeEventChannel(eventChannel: mockEventChannel) + + channel.setStreamHandler(nil) { + expectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } +} From e50b1f3ba5199cede0bbc94390ac910ffa8574b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Fri, 14 Feb 2025 13:38:32 +0100 Subject: [PATCH 23/40] Migrate StreamingTests to Swift --- .../ios/Runner.xcodeproj/project.pbxproj | 16 +-- .../example/ios/RunnerTests/StreamingTest.m | 108 ------------------ .../ios/RunnerTests/StreamingTests.swift | 91 +++++++++++++++ 3 files changed, 96 insertions(+), 119 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTest.m create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index a0d2b33dcee..5bcca8d8e22 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -11,7 +11,6 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 54D650172516862D30686934 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ECAF63F924EFA2D68883BA85 /* libPods-Runner.a */; }; - 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 788A065927B0E02900533D74 /* StreamingTest.m */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 7F29EB222D269ED500740257 /* MockEventChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F29EB212D269ED500740257 /* MockEventChannel.m */; }; 7F29EB292D26A59000740257 /* MockCameraDeviceDiscoverer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F29EB282D26A59000740257 /* MockCameraDeviceDiscoverer.m */; }; @@ -34,6 +33,7 @@ 977A25242D5A511600931E34 /* CameraPermissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25232D5A511600931E34 /* CameraPermissionTests.swift */; }; 977CAC9F2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 978D90B42D5F630300CD817E /* StreamingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978D90B32D5F630300CD817E /* StreamingTests.swift */; }; 979B3DFB2D5B6BC7009BDE1A /* ExceptionCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */; }; 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; @@ -81,7 +81,6 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4A191381C3593DF1AC4E7559 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 788A065927B0E02900533D74 /* StreamingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StreamingTest.m; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -111,8 +110,6 @@ 7FD582362D57D989003B1200 /* MockCaptureDeviceFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureDeviceFormat.h; sourceTree = ""; }; 7FD83D292D5BA49100F4DB7C /* MockCaptureConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureConnection.h; sourceTree = ""; }; 7FD83D2A2D5BA65B00F4DB7C /* MockCaptureConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureConnection.m; sourceTree = ""; }; - 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureDeviceFormat.m; sourceTree = ""; }; - 7FD582362D57D989003B1200 /* MockCaptureDeviceFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureDeviceFormat.h; sourceTree = ""; }; 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPropertiesTests.swift; sourceTree = ""; }; 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueUtilsTests.swift; sourceTree = ""; }; 972CA9302D5A366C004B846F /* CameraExposureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraExposureTests.swift; sourceTree = ""; }; @@ -122,6 +119,7 @@ 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraFocusTests.swift; sourceTree = ""; }; 977A25232D5A511600931E34 /* CameraPermissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPermissionTests.swift; sourceTree = ""; }; 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSafeEventChannelTests.swift; sourceTree = ""; }; + 978D90B32D5F630300CD817E /* StreamingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamingTests.swift; sourceTree = ""; }; 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = ""; }; 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExceptionCatcher.m; sourceTree = ""; }; 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; @@ -175,14 +173,10 @@ children = ( 7F29EB3F2D281C6D00740257 /* Mocks */, 03BB766C2665316900CE5A93 /* Info.plist */, - E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */, - 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */, - E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */, E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */, E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */, E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */, E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */, - 788A065927B0E02900533D74 /* StreamingTest.m */, 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */, 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */, 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */, @@ -200,6 +194,7 @@ 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */, 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */, 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */, + 978D90B32D5F630300CD817E /* StreamingTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -552,11 +547,10 @@ 972CA9312D5A366C004B846F /* CameraExposureTests.swift in Sources */, 7F29EB292D26A59000740257 /* MockCameraDeviceDiscoverer.m in Sources */, 97BD4A102D5CE13500F857D5 /* CameraSessionPresetsTests.swift in Sources */, - 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */, 7FD582272D57C020003B1200 /* MockAssetWriter.m in Sources */, - E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */, 977A25222D5A49EC00931E34 /* CameraFocusTests.swift in Sources */, + 978D90B42D5F630300CD817E /* StreamingTests.swift in Sources */, 7F29EB412D281C7E00740257 /* MockCaptureSession.m in Sources */, 7FD582122D579650003B1200 /* MockWritableData.m in Sources */, 7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTest.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTest.m deleted file mode 100644 index a3786b2079d..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTest.m +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import XCTest; -@import AVFoundation; - -#import "CameraTestUtils.h" -#import "MockFlutterBinaryMessenger.h" - -@interface MockImageStreamHandler : FLTImageStreamHandler -@property(nonatomic, copy) void (^eventSinkStub)(id event); -@end - -@implementation MockImageStreamHandler - -- (FlutterEventSink)eventSink { - if (self.eventSinkStub) { - return ^(id event) { - self.eventSinkStub(event); - }; - } - return nil; -} - -@end - -@interface StreamingTests : XCTestCase -@property(readonly, nonatomic) FLTCam *camera; -@property(readonly, nonatomic) CMSampleBufferRef sampleBuffer; -@end - -@implementation StreamingTests - -- (void)setUp { - dispatch_queue_t captureSessionQueue = dispatch_queue_create("testing", NULL); - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.captureSessionQueue = captureSessionQueue; - - _camera = FLTCreateCamWithConfiguration(configuration); - _sampleBuffer = FLTCreateTestSampleBuffer(); -} - -- (void)tearDown { - CFRelease(_sampleBuffer); -} - -- (void)testExceedMaxStreamingPendingFramesCount { - XCTestExpectation *streamingExpectation = [self - expectationWithDescription:@"Must not call handler over maxStreamingPendingFramesCount"]; - - MockImageStreamHandler *handlerMock = [[MockImageStreamHandler alloc] init]; - handlerMock.eventSinkStub = ^(id event) { - [streamingExpectation fulfill]; - }; - - MockFlutterBinaryMessenger *messenger = [[MockFlutterBinaryMessenger alloc] init]; - [_camera startImageStreamWithMessenger:messenger imageStreamHandler:handlerMock]; - - XCTKVOExpectation *expectation = [[XCTKVOExpectation alloc] initWithKeyPath:@"isStreamingImages" - object:_camera - expectedValue:@YES]; - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[ expectation ] timeout:1]; - XCTAssertEqual(result, XCTWaiterResultCompleted); - - streamingExpectation.expectedFulfillmentCount = 4; - for (int i = 0; i < 10; i++) { - [_camera captureOutput:nil didOutputSampleBuffer:self.sampleBuffer fromConnection:nil]; - } - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testReceivedImageStreamData { - XCTestExpectation *streamingExpectation = - [self expectationWithDescription: - @"Must be able to call the handler again when receivedImageStreamData is called"]; - - MockImageStreamHandler *handlerMock = [[MockImageStreamHandler alloc] init]; - handlerMock.eventSinkStub = ^(id event) { - [streamingExpectation fulfill]; - }; - - MockFlutterBinaryMessenger *messenger = [[MockFlutterBinaryMessenger alloc] init]; - [_camera startImageStreamWithMessenger:messenger imageStreamHandler:handlerMock]; - - XCTKVOExpectation *expectation = [[XCTKVOExpectation alloc] initWithKeyPath:@"isStreamingImages" - object:_camera - expectedValue:@YES]; - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[ expectation ] timeout:1]; - XCTAssertEqual(result, XCTWaiterResultCompleted); - - streamingExpectation.expectedFulfillmentCount = 5; - for (int i = 0; i < 10; i++) { - [_camera captureOutput:nil didOutputSampleBuffer:self.sampleBuffer fromConnection:nil]; - } - - [_camera receivedImageStreamData]; - [_camera captureOutput:nil didOutputSampleBuffer:self.sampleBuffer fromConnection:nil]; - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift new file mode 100644 index 00000000000..2acae46f983 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift @@ -0,0 +1,91 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import AVFoundation +import XCTest + +@testable import camera_avfoundation + +private class MockImageStreamHandler: FLTImageStreamHandler { + var eventSinkStub: ((Any?) -> Void)? + + override var eventSink: FlutterEventSink? { + get { + if let stub = eventSinkStub { + return { event in + stub(event) + } + } + return nil + } + set { + eventSinkStub = newValue + } + } + +} + +final class StreamingTests: XCTestCase { + private func createCamera() -> (FLTCam, CMSampleBuffer) { + let captureSessionQueue = DispatchQueue(label: "testing") + let configuration = FLTCreateTestCameraConfiguration() + configuration.captureSessionQueue = captureSessionQueue + + let camera = FLTCreateCamWithConfiguration(configuration) + let sampleBuffer = FLTCreateTestSampleBuffer().takeRetainedValue() + + return (camera, sampleBuffer) + } + + func testExceedMaxStreamingPendingFramesCount() { + let (camera, sampleBuffer) = createCamera() + let streamingExpectation = expectation( + description: "Must not call handler over maxStreamingPendingFramesCount") + let handlerMock = MockImageStreamHandler() + handlerMock.eventSinkStub = { event in + streamingExpectation.fulfill() + } + let messenger = MockFlutterBinaryMessenger() + camera.startImageStream(with: messenger, imageStreamHandler: handlerMock) + + let expectation = XCTKVOExpectation( + keyPath: "isStreamingImages", object: camera, expectedValue: true) + let result = XCTWaiter.wait(for: [expectation], timeout: 1) + XCTAssertEqual(result, .completed) + + streamingExpectation.expectedFulfillmentCount = 4 + for _ in 0..<10 { + camera.captureOutput(nil, didOutputSampleBuffer: sampleBuffer, from: nil) + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testReceivedImageStreamData() { + let (camera, sampleBuffer) = createCamera() + let streamingExpectation = expectation( + description: "Must be able to call the handler again when receivedImageStreamData is called") + let handlerMock = MockImageStreamHandler() + handlerMock.eventSinkStub = { event in + streamingExpectation.fulfill() + } + let messenger = MockFlutterBinaryMessenger() + camera.startImageStream(with: messenger, imageStreamHandler: handlerMock) + + let expectation = XCTKVOExpectation( + keyPath: "isStreamingImages", object: camera, expectedValue: true) + let result = XCTWaiter.wait(for: [expectation], timeout: 1) + XCTAssertEqual(result, .completed) + + streamingExpectation.expectedFulfillmentCount = 5 + for _ in 0..<10 { + camera.captureOutput(nil, didOutputSampleBuffer: sampleBuffer, from: nil) + } + + camera.receivedImageStreamData() + camera.captureOutput(nil, didOutputSampleBuffer: sampleBuffer, from: nil) + + waitForExpectations(timeout: 30, handler: nil) + } +} From 550a630fdee3b54d0a65dd40e2eef9d9d4a93a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Mon, 17 Feb 2025 17:13:22 +0100 Subject: [PATCH 24/40] Migrate SampleBufferTests to Swift --- .../ios/Runner.xcodeproj/project.pbxproj | 8 +- .../ios/RunnerTests/FLTCamSampleBufferTests.m | 394 ------------------ .../RunnerTests/RunnerTests-Bridging-Header.h | 1 + .../ios/RunnerTests/SampleBufferTests.swift | 304 ++++++++++++++ 4 files changed, 309 insertions(+), 398 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index 5bcca8d8e22..60f84ad56c3 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ 977CAC9F2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 978D90B42D5F630300CD817E /* StreamingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978D90B32D5F630300CD817E /* StreamingTests.swift */; }; + 97922B0D2D6380C300A9B4CF /* SampleBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97922B0C2D6380C300A9B4CF /* SampleBufferTests.swift */; }; 979B3DFB2D5B6BC7009BDE1A /* ExceptionCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */; }; 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; @@ -47,7 +48,6 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97DB234D2D566D0700CEFE66 /* CameraPreviewPauseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DB234C2D566D0700CEFE66 /* CameraPreviewPauseTests.swift */; }; E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */; }; - E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */; }; E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */; }; /* End PBXBuildFile section */ @@ -120,6 +120,7 @@ 977A25232D5A511600931E34 /* CameraPermissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPermissionTests.swift; sourceTree = ""; }; 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSafeEventChannelTests.swift; sourceTree = ""; }; 978D90B32D5F630300CD817E /* StreamingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamingTests.swift; sourceTree = ""; }; + 97922B0C2D6380C300A9B4CF /* SampleBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleBufferTests.swift; sourceTree = ""; }; 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = ""; }; 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExceptionCatcher.m; sourceTree = ""; }; 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; @@ -140,7 +141,6 @@ A8F314CD1C64E9257EBC811D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; B61D98BBC8FB276D1C4A7BB2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCamPhotoCaptureTests.m; sourceTree = ""; }; - E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTCamSampleBufferTests.m; sourceTree = ""; }; E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraTestUtils.h; sourceTree = ""; }; E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraTestUtils.m; sourceTree = ""; }; E67C6DBF6478BE708993169F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; @@ -174,7 +174,6 @@ 7F29EB3F2D281C6D00740257 /* Mocks */, 03BB766C2665316900CE5A93 /* Info.plist */, E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */, - E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */, E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */, E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */, 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */, @@ -195,6 +194,7 @@ 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */, 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */, 978D90B32D5F630300CD817E /* StreamingTests.swift */, + 97922B0C2D6380C300A9B4CF /* SampleBufferTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -533,11 +533,11 @@ 7FD83D2B2D5BA65B00F4DB7C /* MockCaptureConnection.m in Sources */, 977A25242D5A511600931E34 /* CameraPermissionTests.swift in Sources */, 7F8FD2292D4BFABF001AF2C1 /* MockGlobalEventApi.m in Sources */, - E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */, 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */, 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */, 7F29EB222D269ED500740257 /* MockEventChannel.m in Sources */, 7F8FD22F2D4D0B88001AF2C1 /* MockFlutterBinaryMessenger.m in Sources */, + 97922B0D2D6380C300A9B4CF /* SampleBufferTests.swift in Sources */, 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */, E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */, 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m deleted file mode 100644 index 83fbd57504c..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m +++ /dev/null @@ -1,394 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import AVFoundation; -@import XCTest; -#import "CameraTestUtils.h" -#import "MockAssetWriter.h" -#import "MockCaptureConnection.h" - -@import camera_avfoundation; -@import AVFoundation; - -@interface FakeMediaSettingsAVWrapper : FLTCamMediaSettingsAVWrapper -@property(readonly, nonatomic) MockAssetWriterInput *inputMock; -@end - -@implementation FakeMediaSettingsAVWrapper -- (instancetype)initWithInputMock:(MockAssetWriterInput *)inputMock { - _inputMock = inputMock; - return self; -} - -- (BOOL)lockDevice:(AVCaptureDevice *)captureDevice error:(NSError **)outError { - return YES; -} - -- (void)unlockDevice:(AVCaptureDevice *)captureDevice { -} - -- (void)beginConfigurationForSession:(id)videoCaptureSession { -} - -- (void)commitConfigurationForSession:(id)videoCaptureSession { -} - -- (void)setMinFrameDuration:(CMTime)duration onDevice:(AVCaptureDevice *)captureDevice { -} - -- (void)setMaxFrameDuration:(CMTime)duration onDevice:(AVCaptureDevice *)captureDevice { -} - -- (id)assetWriterAudioInputWithOutputSettings: - (nullable NSDictionary *)outputSettings { - return _inputMock; -} - -- (id)assetWriterVideoInputWithOutputSettings: - (nullable NSDictionary *)outputSettings { - return _inputMock; -} - -- (void)addInput:(AVAssetWriterInput *)writerInput toAssetWriter:(AVAssetWriter *)writer { -} - -- (NSDictionary *) - recommendedVideoSettingsForAssetWriterWithFileType:(AVFileType)fileType - forOutput:(AVCaptureVideoDataOutput *)output { - return @{}; -} -@end - -/// Includes test cases related to sample buffer handling for FLTCam class. -@interface FLTCamSampleBufferTests : XCTestCase -@end - -@implementation FLTCamSampleBufferTests - -- (FLTCam *)createCameraWithAssetWriter:(MockAssetWriter *)assetWriter - adaptor:(MockAssetWriterInputPixelBufferAdaptor *)adaptor - input:(MockAssetWriterInput *)input { - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.mediaSettings = - [FCPPlatformMediaSettings makeWithResolutionPreset:FCPPlatformResolutionPresetMedium - framesPerSecond:nil - videoBitrate:nil - audioBitrate:nil - enableAudio:YES]; - configuration.mediaSettingsWrapper = [[FakeMediaSettingsAVWrapper alloc] initWithInputMock:input]; - - configuration.assetWriterFactory = - ^NSObject *_Nonnull(NSURL *url, AVFileType fileType, NSError **error) { - return assetWriter; - }; - configuration.inputPixelBufferAdaptorFactory = - ^id _Nonnull(NSObject *input, - NSDictionary *settings) { - return adaptor; - }; - - return FLTCreateCamWithConfiguration(configuration); -} - -- (void)testSampleBufferCallbackQueueMustBeCaptureSessionQueue { - dispatch_queue_t captureSessionQueue = dispatch_queue_create("testing", NULL); - FLTCam *camera = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue); - XCTAssertEqual(captureSessionQueue, camera.captureVideoOutput.sampleBufferCallbackQueue, - @"Sample buffer callback queue must be the capture session queue."); -} - -- (void)testCopyPixelBuffer { - FLTCam *camera = FLTCreateCamWithConfiguration(FLTCreateTestCameraConfiguration()); - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - CMSampleBufferRef capturedSampleBuffer = FLTCreateTestSampleBuffer(); - CVPixelBufferRef capturedPixelBuffer = CMSampleBufferGetImageBuffer(capturedSampleBuffer); - // Mimic sample buffer callback when captured a new video sample - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:capturedSampleBuffer - fromConnection:connectionMock]; - CVPixelBufferRef deliveriedPixelBuffer = [camera copyPixelBuffer]; - XCTAssertEqual(deliveriedPixelBuffer, capturedPixelBuffer, - @"FLTCam must deliver the latest captured pixel buffer to copyPixelBuffer API."); - CFRelease(capturedSampleBuffer); - CFRelease(deliveriedPixelBuffer); -} - -- (void)testDidOutputSampleBuffer_mustNotChangeSampleBufferRetainCountAfterPauseResumeRecording { - FLTCam *camera = FLTCreateCamWithConfiguration(FLTCreateTestCameraConfiguration()); - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - CMSampleBufferRef sampleBuffer = FLTCreateTestSampleBuffer(); - - // Pause then resume the recording. - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - [camera pauseVideoRecording]; - [camera resumeVideoRecording]; - - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:sampleBuffer - fromConnection:connectionMock]; - XCTAssertEqual(CFGetRetainCount(sampleBuffer), 1, - @"didOutputSampleBuffer must not change the sample buffer retain count after " - @"pause resume recording."); - CFRelease(sampleBuffer); -} - -- (void)testDidOutputSampleBufferIgnoreAudioSamplesBeforeVideoSamples { - MockAssetWriter *writerMock = [[MockAssetWriter alloc] init]; - MockAssetWriterInputPixelBufferAdaptor *adaptorMock = - [[MockAssetWriterInputPixelBufferAdaptor alloc] init]; - MockAssetWriterInput *inputMock = [[MockAssetWriterInput alloc] init]; - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - - __block AVAssetWriterStatus status = AVAssetWriterStatusUnknown; - writerMock.startWritingStub = ^{ - status = AVAssetWriterStatusWriting; - }; - writerMock.statusStub = ^AVAssetWriterStatus { - return status; - }; - - FLTCam *camera = [self createCameraWithAssetWriter:writerMock - adaptor:adaptorMock - input:inputMock]; - CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer(); - CMSampleBufferRef audioSample = FLTCreateTestAudioSampleBuffer(); - - __block NSArray *writtenSamples = @[]; - - adaptorMock.appendPixelBufferStub = ^BOOL(CVPixelBufferRef buffer, CMTime time) { - writtenSamples = [writtenSamples arrayByAddingObject:@"video"]; - return YES; - }; - - inputMock.readyForMoreMediaData = YES; - inputMock.appendSampleBufferStub = ^BOOL(CMSampleBufferRef buffer) { - writtenSamples = [writtenSamples arrayByAddingObject:@"audio"]; - return YES; - }; - - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - [camera captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - [camera captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - [camera captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - - NSArray *expectedSamples = @[ @"video", @"audio" ]; - XCTAssertEqualObjects(writtenSamples, expectedSamples, @"First appended sample must be video."); - - CFRelease(videoSample); - CFRelease(audioSample); -} - -- (void)testDidOutputSampleBufferSampleTimesMustBeNumericAfterPauseResume { - MockAssetWriter *writerMock = [[MockAssetWriter alloc] init]; - MockAssetWriterInputPixelBufferAdaptor *adaptorMock = - [[MockAssetWriterInputPixelBufferAdaptor alloc] init]; - MockAssetWriterInput *inputMock = [[MockAssetWriterInput alloc] init]; - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - - FLTCam *camera = [self createCameraWithAssetWriter:writerMock - adaptor:adaptorMock - input:inputMock]; - CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer(); - CMSampleBufferRef audioSample = FLTCreateTestAudioSampleBuffer(); - - __block AVAssetWriterStatus status = AVAssetWriterStatusUnknown; - writerMock.startWritingStub = ^{ - status = AVAssetWriterStatusWriting; - }; - writerMock.statusStub = ^AVAssetWriterStatus { - return status; - }; - - __block BOOL videoAppended = NO; - adaptorMock.appendPixelBufferStub = ^BOOL(CVPixelBufferRef buffer, CMTime time) { - XCTAssert(CMTIME_IS_NUMERIC(time)); - videoAppended = YES; - return YES; - }; - - __block BOOL audioAppended = NO; - inputMock.readyForMoreMediaData = YES; - inputMock.appendSampleBufferStub = ^BOOL(CMSampleBufferRef buffer) { - CMTime sampleTime = CMSampleBufferGetPresentationTimeStamp(buffer); - XCTAssert(CMTIME_IS_NUMERIC(sampleTime)); - audioAppended = YES; - return YES; - }; - - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - [camera pauseVideoRecording]; - [camera resumeVideoRecording]; - - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - [camera captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - [camera captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - XCTAssert(videoAppended && audioAppended, @"Video or audio was not appended."); - - CFRelease(videoSample); - CFRelease(audioSample); -} - -- (void)testDidOutputSampleBufferMustNotAppendSampleWhenReadyForMoreMediaDataIsNo { - MockAssetWriter *writerMock = [[MockAssetWriter alloc] init]; - MockAssetWriterInputPixelBufferAdaptor *adaptorMock = - [[MockAssetWriterInputPixelBufferAdaptor alloc] init]; - MockAssetWriterInput *inputMock = [[MockAssetWriterInput alloc] init]; - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - FLTCam *camera = [self createCameraWithAssetWriter:writerMock - adaptor:adaptorMock - input:inputMock]; - - CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer(); - - __block BOOL sampleAppended = NO; - adaptorMock.appendPixelBufferStub = ^BOOL(CVPixelBufferRef buffer, CMTime time) { - sampleAppended = YES; - return YES; - }; - - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - inputMock.readyForMoreMediaData = YES; - sampleAppended = NO; - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssertTrue(sampleAppended, @"Sample was not appended."); - - inputMock.readyForMoreMediaData = NO; - sampleAppended = NO; - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssertFalse(sampleAppended, @"Sample cannot be appended when readyForMoreMediaData is NO."); - - CFRelease(videoSample); -} - -- (void)testStopVideoRecordingWithCompletionMustCallCompletion { - MockAssetWriter *writerMock = [[MockAssetWriter alloc] init]; - MockAssetWriterInputPixelBufferAdaptor *adaptorMock = - [[MockAssetWriterInputPixelBufferAdaptor alloc] init]; - MockAssetWriterInput *inputMock = [[MockAssetWriterInput alloc] init]; - FLTCam *camera = [self createCameraWithAssetWriter:writerMock - adaptor:adaptorMock - input:inputMock]; - - __block AVAssetWriterStatus status = AVAssetWriterStatusUnknown; - writerMock.startWritingStub = ^{ - status = AVAssetWriterStatusWriting; - }; - writerMock.statusStub = ^AVAssetWriterStatus { - return status; - }; - writerMock.finishWritingStub = ^(void (^param)(void)) { - XCTAssert(writerMock.status == AVAssetWriterStatusWriting, - @"Cannot call finishWritingWithCompletionHandler when status is " - @"not AVAssetWriterStatusWriting."); - void (^handler)(void) = param; - handler(); - }; - - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - __block BOOL completionCalled = NO; - [camera - stopVideoRecordingWithCompletion:^(NSString *_Nullable path, FlutterError *_Nullable error) { - completionCalled = YES; - }]; - XCTAssert(completionCalled, @"Completion was not called."); -} - -- (void)testStartWritingShouldNotBeCalledBetweenSampleCreationAndAppending { - MockAssetWriter *writerMock = [[MockAssetWriter alloc] init]; - MockAssetWriterInputPixelBufferAdaptor *adaptorMock = - [[MockAssetWriterInputPixelBufferAdaptor alloc] init]; - MockAssetWriterInput *inputMock = [[MockAssetWriterInput alloc] init]; - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - FLTCam *camera = [self createCameraWithAssetWriter:writerMock - adaptor:adaptorMock - input:inputMock]; - - CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer(); - - __block BOOL startWritingCalled = NO; - writerMock.startWritingStub = ^{ - startWritingCalled = YES; - }; - - __block BOOL videoAppended = NO; - adaptorMock.appendPixelBufferStub = ^BOOL(CVPixelBufferRef buffer, CMTime time) { - videoAppended = YES; - return YES; - }; - - inputMock.readyForMoreMediaData = YES; - - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - BOOL startWritingCalledBefore = startWritingCalled; - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssert((startWritingCalledBefore && videoAppended) || (startWritingCalled && !videoAppended), - @"The startWriting was called between sample creation and appending."); - - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssert(videoAppended, @"Video was not appended."); - - CFRelease(videoSample); -} - -- (void)testStartVideoRecordingWithCompletionShouldNotDisableMixWithOthers { - FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL)); - - [AVAudioSession.sharedInstance setCategory:AVAudioSessionCategoryPlayback - withOptions:AVAudioSessionCategoryOptionMixWithOthers - error:nil]; - [cam - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - XCTAssert( - AVAudioSession.sharedInstance.categoryOptions & AVAudioSessionCategoryOptionMixWithOthers, - @"Flag MixWithOthers was removed."); - XCTAssert(AVAudioSession.sharedInstance.category == AVAudioSessionCategoryPlayAndRecord, - @"Category should be PlayAndRecord."); -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index 4e0309c4909..c6bc263b494 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -12,6 +12,7 @@ // Mocks, protocols. #import "MockAssetWriter.h" #import "MockCameraDeviceDiscoverer.h" +#import "MockCaptureConnection.h" #import "MockCaptureDevice.h" #import "MockCaptureDeviceFormat.h" #import "MockCaptureSession.h" diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift new file mode 100644 index 00000000000..96154213f98 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift @@ -0,0 +1,304 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import AVFoundation +import XCTest + +@testable import camera_avfoundation + +private class FakeMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { + let inputMock: MockAssetWriterInput + + init(inputMock: MockAssetWriterInput) { + self.inputMock = inputMock + } + + override func lockDevice(_ captureDevice: FLTCaptureDevice) throws { + // No-op. + } + + override func unlockDevice(_ captureDevice: FLTCaptureDevice) { + // No-op. + } + + override func beginConfiguration(for videoCaptureSession: any FLTCaptureSession) { + // No-op. + } + + override func commitConfiguration(for videoCaptureSession: any FLTCaptureSession) { + // No-op. + } + + override func setMinFrameDuration(_ duration: CMTime, on captureDevice: any FLTCaptureDevice) { + // No-op. + } + + override func setMaxFrameDuration(_ duration: CMTime, on captureDevice: any FLTCaptureDevice) { + // No-op. + } + + override func assetWriterAudioInput(withOutputSettings outputSettings: [String: Any]?) + -> FLTAssetWriterInput + { + return inputMock + } + + override func assetWriterVideoInput(withOutputSettings outputSettings: [String: Any]?) + -> FLTAssetWriterInput + { + return inputMock + } + + override func addInput(_ writerInput: any FLTAssetWriterInput, to writer: any FLTAssetWriter) { + // No-op. + } + + override func recommendedVideoSettingsForAssetWriter( + withFileType fileType: AVFileType, for output: AVCaptureVideoDataOutput + ) -> [String: Any]? { + return [:] + } +} + +/// Includes test cases related to sample buffer handling for FLTCam class. +final class CameraSampleBufferTests: XCTestCase { + private func createCamera() -> (FLTCam, MockAssetWriter, MockAssetWriterInputPixelBufferAdaptor, MockAssetWriterInput, MockCaptureConnection) { + let assetWriter = MockAssetWriter() + let adaptor = MockAssetWriterInputPixelBufferAdaptor() + let input = MockAssetWriterInput() + + let configuration = FLTCreateTestCameraConfiguration() + configuration.mediaSettings = FCPPlatformMediaSettings.make( + with: .medium, framesPerSecond: nil, videoBitrate: nil, audioBitrate: nil, enableAudio: true) + configuration.mediaSettingsWrapper = FakeMediaSettingsAVWrapper(inputMock: input) + + configuration.assetWriterFactory = { url, fileType, error in + return assetWriter + } + configuration.inputPixelBufferAdaptorFactory = { input, settings in + return adaptor + } + + return (FLTCreateCamWithConfiguration(configuration), assetWriter, adaptor, input, MockCaptureConnection()) + } + + func testSampleBufferCallbackQueueMustBeCaptureSessionQueue() { + let captureSessionQueue = DispatchQueue(label: "testing") + let camera = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue) + XCTAssertEqual( + captureSessionQueue, camera.captureVideoOutput.sampleBufferCallbackQueue, + "Sample buffer callback queue must be the capture session queue.") + } + + func testCopyPixelBuffer() { + let (camera, _, _, _, connectionMock) = createCamera() + let capturedSampleBuffer = FLTCreateTestSampleBuffer().takeRetainedValue() + let capturedPixelBuffer = CMSampleBufferGetImageBuffer(capturedSampleBuffer)! + // Mimic sample buffer callback when captured a new video sample. + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: capturedSampleBuffer, from: connectionMock) + let deliveredPixelBuffer = camera.copyPixelBuffer()?.takeRetainedValue() + XCTAssertEqual( + deliveredPixelBuffer, capturedPixelBuffer, + "FLTCam must deliver the latest captured pixel buffer to copyPixelBuffer API.") + } + + func testDidOutputSampleBuffer_mustNotChangeSampleBufferRetainCountAfterPauseResumeRecording() { + let (camera, _, _, _, connectionMock) = createCamera() + let sampleBuffer = FLTCreateTestSampleBuffer().takeRetainedValue() + + let initialRetainCount = CFGetRetainCount(sampleBuffer) + + // Pause then resume the recording. + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + camera.pauseVideoRecording() + camera.resumeVideoRecording() + + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: sampleBuffer, from: connectionMock) + + let finalRetainCount = CFGetRetainCount(sampleBuffer) + XCTAssertEqual( + finalRetainCount, initialRetainCount, + "didOutputSampleBuffer must not change the sample buffer retain count after pause resume recording." + ) + } + + func testDidOutputSampleBufferIgnoreAudioSamplesBeforeVideoSamples() { + let (camera, writerMock, adaptorMock, inputMock, connectionMock) = createCamera() + + var status = AVAssetWriter.Status.unknown + writerMock.startWritingStub = { + status = .writing + } + writerMock.statusStub = { + return status + } + + let videoSample = FLTCreateTestSampleBuffer().takeRetainedValue() + let audioSample = FLTCreateTestAudioSampleBuffer().takeRetainedValue() + + var writtenSamples: [String] = [] + adaptorMock.appendPixelBufferStub = { buffer, time in + writtenSamples.append("video") + return true + } + inputMock.readyForMoreMediaData = true + inputMock.appendSampleBufferStub = { buffer in + writtenSamples.append("audio") + return true + } + + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + camera.captureOutput(nil, didOutputSampleBuffer: audioSample, from: connectionMock) + camera.captureOutput(nil, didOutputSampleBuffer: audioSample, from: connectionMock) + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + camera.captureOutput(nil, didOutputSampleBuffer: audioSample, from: connectionMock) + + let expectedSamples = ["video", "audio"] + XCTAssertEqual(writtenSamples, expectedSamples, "First appended sample must be video.") + } + + func testDidOutputSampleBufferSampleTimesMustBeNumericAfterPauseResume() { + let (camera, writerMock, adaptorMock, inputMock, connectionMock) = createCamera() + + let videoSample = FLTCreateTestSampleBuffer().takeRetainedValue() + let audioSample = FLTCreateTestAudioSampleBuffer().takeRetainedValue() + + var status = AVAssetWriter.Status.unknown + writerMock.startWritingStub = { + status = .writing + } + writerMock.statusStub = { + return status + } + + var videoAppended = false + adaptorMock.appendPixelBufferStub = { buffer, time in + XCTAssert(CMTIME_IS_NUMERIC(time)) + videoAppended = true + return true + } + + var audioAppended = false + inputMock.readyForMoreMediaData = true + inputMock.appendSampleBufferStub = { buffer in + let sampleTime = CMSampleBufferGetPresentationTimeStamp(buffer!) + XCTAssert(CMTIME_IS_NUMERIC(sampleTime)) + audioAppended = true + return true + } + + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + camera.pauseVideoRecording() + camera.resumeVideoRecording() + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + camera.captureOutput(nil, didOutputSampleBuffer: audioSample, from: connectionMock) + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + camera.captureOutput(nil, didOutputSampleBuffer: audioSample, from: connectionMock) + + XCTAssert(videoAppended && audioAppended, "Video or audio was not appended.") + } + + func testDidOutputSampleBufferMustNotAppendSampleWhenReadyForMoreMediaDataIsFalse() { + let (camera, writerMock, adaptorMock, inputMock, connectionMock) = createCamera() + + let videoSample = FLTCreateTestSampleBuffer().takeRetainedValue() + + var sampleAppended = false + adaptorMock.appendPixelBufferStub = { buffer, time in + sampleAppended = true + return true + } + + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + + inputMock.readyForMoreMediaData = true + sampleAppended = false + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + XCTAssertTrue(sampleAppended, "Sample was not appended.") + + inputMock.readyForMoreMediaData = false + sampleAppended = false + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + XCTAssertFalse(sampleAppended, "Sample cannot be appended when readyForMoreMediaData is NO.") + } + + func testStopVideoRecordingWithCompletionMustCallCompletion() { + let (camera, writerMock, adaptorMock, inputMock, _) = createCamera() + + var status = AVAssetWriter.Status.unknown + writerMock.startWritingStub = { + status = .writing + } + writerMock.statusStub = { + return status + } + writerMock.finishWritingStub = { handler in + XCTAssert( + writerMock.status == .writing, + "Cannot call finishWritingWithCompletionHandler when status is not AVAssetWriter.Status.writing." + ) + handler?() + } + + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + var completionCalled = false + camera.stopVideoRecording(completion: { path, error in + completionCalled = true + }) + + XCTAssert(completionCalled, "Completion was not called.") + } + + func testStartWritingShouldNotBeCalledBetweenSampleCreationAndAppending() { + let (camera, writerMock, adaptorMock, inputMock, connectionMock) = createCamera() + + let videoSample = FLTCreateTestSampleBuffer().takeRetainedValue() + + var startWritingCalled = false + writerMock.startWritingStub = { + startWritingCalled = true + } + + var videoAppended = false + adaptorMock.appendPixelBufferStub = { buffer, time in + videoAppended = true + return true + } + + inputMock.readyForMoreMediaData = true + + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + + let startWritingCalledBefore = startWritingCalled + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + XCTAssert( + (startWritingCalledBefore && videoAppended) || (startWritingCalled && !videoAppended), + "The startWriting was called between sample creation and appending.") + + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + XCTAssert(videoAppended, "Video was not appended.") + } + + func testStartVideoRecordingWithCompletionShouldNotDisableMixWithOthers() { + let cam = FLTCreateCamWithCaptureSessionQueue(DispatchQueue(label: "testing")) + + try? AVAudioSession.sharedInstance().setCategory(.playback, options: .mixWithOthers) + cam.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + XCTAssert( + AVAudioSession.sharedInstance().categoryOptions.contains(.mixWithOthers), + "Flag MixWithOthers was removed.") + XCTAssert( + AVAudioSession.sharedInstance().category == .playAndRecord, + "Category should be PlayAndRecord.") + } +} From bcb050d95bbdb78195f3cb0b6a7481f8fdf98eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Tue, 18 Feb 2025 16:17:16 +0100 Subject: [PATCH 25/40] Migrate PhotoCaptureTests to Swift --- .../ios/Runner.xcodeproj/project.pbxproj | 9 +- .../example/ios/RunnerTests/CameraTestUtils.h | 7 + .../example/ios/RunnerTests/CameraTestUtils.m | 4 + .../ios/RunnerTests/FLTCamPhotoCaptureTests.m | 216 ------------------ .../ios/RunnerTests/PhotoCaptureTests.swift | 194 ++++++++++++++++ .../RunnerTests/RunnerTests-Bridging-Header.h | 2 + .../ios/RunnerTests/SampleBufferTests.swift | 14 +- 7 files changed, 221 insertions(+), 225 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index 60f84ad56c3..e85cbd867c1 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 977A25222D5A49EC00931E34 /* CameraFocusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */; }; 977A25242D5A511600931E34 /* CameraPermissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25232D5A511600931E34 /* CameraPermissionTests.swift */; }; 977CAC9F2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */; }; + 978296CF2D5F744B0009BDD3 /* PhotoCaptureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978296CE2D5F744B0009BDD3 /* PhotoCaptureTests.swift */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 978D90B42D5F630300CD817E /* StreamingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978D90B32D5F630300CD817E /* StreamingTests.swift */; }; 97922B0D2D6380C300A9B4CF /* SampleBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97922B0C2D6380C300A9B4CF /* SampleBufferTests.swift */; }; @@ -47,7 +48,6 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97DB234D2D566D0700CEFE66 /* CameraPreviewPauseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DB234C2D566D0700CEFE66 /* CameraPreviewPauseTests.swift */; }; - E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */; }; E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */; }; /* End PBXBuildFile section */ @@ -119,6 +119,7 @@ 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraFocusTests.swift; sourceTree = ""; }; 977A25232D5A511600931E34 /* CameraPermissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPermissionTests.swift; sourceTree = ""; }; 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSafeEventChannelTests.swift; sourceTree = ""; }; + 978296CE2D5F744B0009BDD3 /* PhotoCaptureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCaptureTests.swift; sourceTree = ""; }; 978D90B32D5F630300CD817E /* StreamingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamingTests.swift; sourceTree = ""; }; 97922B0C2D6380C300A9B4CF /* SampleBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleBufferTests.swift; sourceTree = ""; }; 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = ""; }; @@ -140,7 +141,6 @@ 9DDC4CE84A8B378AE4A8CD9C /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A8F314CD1C64E9257EBC811D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; B61D98BBC8FB276D1C4A7BB2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCamPhotoCaptureTests.m; sourceTree = ""; }; E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraTestUtils.h; sourceTree = ""; }; E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraTestUtils.m; sourceTree = ""; }; E67C6DBF6478BE708993169F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; @@ -173,7 +173,6 @@ children = ( 7F29EB3F2D281C6D00740257 /* Mocks */, 03BB766C2665316900CE5A93 /* Info.plist */, - E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */, E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */, E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */, 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */, @@ -195,6 +194,7 @@ 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */, 978D90B32D5F630300CD817E /* StreamingTests.swift */, 97922B0C2D6380C300A9B4CF /* SampleBufferTests.swift */, + 978296CE2D5F744B0009BDD3 /* PhotoCaptureTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -525,11 +525,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */, 97BD4A0E2D5CC5AE00F857D5 /* CameraSettingsTests.swift in Sources */, 972CA92D2D5A28C4004B846F /* QueueUtilsTests.swift in Sources */, 979B3DFB2D5B6BC7009BDE1A /* ExceptionCatcher.m in Sources */, - E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */, 7FD83D2B2D5BA65B00F4DB7C /* MockCaptureConnection.m in Sources */, 977A25242D5A511600931E34 /* CameraPermissionTests.swift in Sources */, 7F8FD2292D4BFABF001AF2C1 /* MockGlobalEventApi.m in Sources */, @@ -540,6 +538,7 @@ 97922B0D2D6380C300A9B4CF /* SampleBufferTests.swift in Sources */, 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */, E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */, + 978296CF2D5F744B0009BDD3 /* PhotoCaptureTests.swift in Sources */, 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */, 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */, 97DB234D2D566D0700CEFE66 /* CameraPreviewPauseTests.swift in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index a44e3aedcd0..0844b292af5 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -30,4 +30,11 @@ extern CMSampleBufferRef FLTCreateTestSampleBuffer(void); /// @return a test audio sample buffer. extern CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void); +/// Calls `dispatch_queue_set_specific` with a key that is used to identify the queue. +/// This method is needed for comaptibility of Swift tests with Objective-C code. +/// In Swift, the API for settinng key-value pairs on a queue is different, so Swift tests +/// need to call this method to set the key-value pair on the queue in a way that's +/// compatible withn the existing Objective-C code. +extern void dispatchQueueSetSpecific(dispatch_queue_t queue, const void *key); + NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index 4b99e23d11f..2b66cde9cce 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -127,3 +127,7 @@ CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void) { CFRelease(formatDescription); return sampleBuffer; } + +void dispatchQueueSetSpecific(dispatch_queue_t queue, const void *key) { + dispatch_queue_set_specific(queue, key, (void *)key, NULL); +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m deleted file mode 100644 index 425481d263d..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import AVFoundation; -@import XCTest; - -#import "CameraTestUtils.h" -#import "MockCaptureDevice.h" -#import "MockCapturePhotoOutput.h" - -/// Includes test cases related to photo capture operations for FLTCam class. -@interface FLTCamPhotoCaptureTests : XCTestCase - -@end - -@implementation FLTCamPhotoCaptureTests - -- (FLTCam *)createCamWithCaptureSessionQueue:(dispatch_queue_t)captureSessionQueue { - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.captureSessionQueue = captureSessionQueue; - return FLTCreateCamWithConfiguration(configuration); -} - -- (void)testCaptureToFile_mustReportErrorToResultIfSavePhotoDelegateCompletionsWithError { - XCTestExpectation *errorExpectation = - [self expectationWithDescription: - @"Must send error to result if save photo delegate completes with error."]; - - dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); - dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, - (void *)FLTCaptureSessionQueueSpecific, NULL); - FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue); - - NSError *error = [NSError errorWithDomain:@"test" code:0 userInfo:nil]; - - MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init]; - mockOutput.capturePhotoWithSettingsStub = - ^(AVCapturePhotoSettings *settings, NSObject *photoDelegate) { - FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; - // Completion runs on IO queue. - dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); - dispatch_async(ioQueue, ^{ - delegate.completionHandler(nil, error); - }); - }; - cam.capturePhotoOutput = mockOutput; - - // `FLTCam::captureToFile` runs on capture session queue. - dispatch_async(captureSessionQueue, ^{ - [cam captureToFileWithCompletion:^(NSString *result, FlutterError *error) { - XCTAssertNil(result); - XCTAssertNotNil(error); - [errorExpectation fulfill]; - }]; - }); - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testCaptureToFile_mustReportPathToResultIfSavePhotoDelegateCompletionsWithPath { - XCTestExpectation *pathExpectation = - [self expectationWithDescription: - @"Must send file path to result if save photo delegate completes with file path."]; - - dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); - dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, - (void *)FLTCaptureSessionQueueSpecific, NULL); - FLTCam *cam = [self createCamWithCaptureSessionQueue:captureSessionQueue]; - - NSString *filePath = @"test"; - - MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init]; - mockOutput.capturePhotoWithSettingsStub = - ^(AVCapturePhotoSettings *settings, NSObject *photoDelegate) { - FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; - // Completion runs on IO queue. - dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); - dispatch_async(ioQueue, ^{ - delegate.completionHandler(filePath, nil); - }); - }; - cam.capturePhotoOutput = mockOutput; - - // `FLTCam::captureToFile` runs on capture session queue. - dispatch_async(captureSessionQueue, ^{ - [cam captureToFileWithCompletion:^(NSString *result, FlutterError *error) { - XCTAssertEqual(result, filePath); - [pathExpectation fulfill]; - }]; - }); - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testCaptureToFile_mustReportFileExtensionWithHeifWhenHEVCIsAvailableAndFileFormatIsHEIF { - XCTestExpectation *expectation = - [self expectationWithDescription: - @"Test must set extension to heif if availablePhotoCodecTypes contains HEVC."]; - dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); - dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, - (void *)FLTCaptureSessionQueueSpecific, NULL); - FLTCam *cam = [self createCamWithCaptureSessionQueue:captureSessionQueue]; - [cam setImageFileFormat:FCPPlatformImageFileFormatHeif]; - - MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init]; - mockOutput.availablePhotoCodecTypes = @[ AVVideoCodecTypeHEVC ]; - mockOutput.capturePhotoWithSettingsStub = - ^(AVCapturePhotoSettings *settings, NSObject *photoDelegate) { - FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; - // Completion runs on IO queue. - dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); - dispatch_async(ioQueue, ^{ - delegate.completionHandler(delegate.filePath, nil); - }); - }; - cam.capturePhotoOutput = mockOutput; - - // `FLTCam::captureToFile` runs on capture session queue. - dispatch_async(captureSessionQueue, ^{ - [cam captureToFileWithCompletion:^(NSString *filePath, FlutterError *error) { - XCTAssertEqualObjects([filePath pathExtension], @"heif"); - [expectation fulfill]; - }]; - }); - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testCaptureToFile_mustReportFileExtensionWithJpgWhenHEVCNotAvailableAndFileFormatIsHEIF { - XCTestExpectation *expectation = [self - expectationWithDescription: - @"Test must set extension to jpg if availablePhotoCodecTypes does not contain HEVC."]; - dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); - dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, - (void *)FLTCaptureSessionQueueSpecific, NULL); - FLTCam *cam = [self createCamWithCaptureSessionQueue:captureSessionQueue]; - [cam setImageFileFormat:FCPPlatformImageFileFormatHeif]; - - MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init]; - mockOutput.capturePhotoWithSettingsStub = - ^(AVCapturePhotoSettings *settings, NSObject *photoDelegate) { - FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; - // Completion runs on IO queue. - dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); - dispatch_async(ioQueue, ^{ - delegate.completionHandler(delegate.filePath, nil); - }); - }; - cam.capturePhotoOutput = mockOutput; - - // `FLTCam::captureToFile` runs on capture session queue. - dispatch_async(captureSessionQueue, ^{ - [cam captureToFileWithCompletion:^(NSString *filePath, FlutterError *error) { - XCTAssertEqualObjects([filePath pathExtension], @"jpg"); - [expectation fulfill]; - }]; - }); - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testCaptureToFile_handlesTorchMode { - XCTestExpectation *pathExpectation = - [self expectationWithDescription: - @"Must send file path to result if save photo delegate completes with file path."]; - XCTestExpectation *setTorchExpectation = - [self expectationWithDescription:@"Should set torch mode to AVCaptureTorchModeOn."]; - - MockCaptureDevice *captureDeviceMock = [[MockCaptureDevice alloc] init]; - captureDeviceMock.hasTorch = YES; - captureDeviceMock.isTorchAvailable = YES; - captureDeviceMock.torchMode = AVCaptureTorchModeAuto; - captureDeviceMock.setTorchModeStub = ^(AVCaptureTorchMode mode) { - if (mode == AVCaptureTorchModeOn) { - [setTorchExpectation fulfill]; - } - }; - - dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); - dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, - (void *)FLTCaptureSessionQueueSpecific, NULL); - - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.captureSessionQueue = captureSessionQueue; - configuration.captureDeviceFactory = ^NSObject * { return captureDeviceMock; }; - FLTCam *cam = FLTCreateCamWithConfiguration(configuration); - - NSString *filePath = @"test"; - - MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init]; - mockOutput.capturePhotoWithSettingsStub = - ^(AVCapturePhotoSettings *settings, NSObject *photoDelegate) { - FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; - // Completion runs on IO queue. - dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); - dispatch_async(ioQueue, ^{ - delegate.completionHandler(filePath, nil); - }); - }; - cam.capturePhotoOutput = mockOutput; - - // `FLTCam::captureToFile` runs on capture session queue. - dispatch_async(captureSessionQueue, ^{ - [cam setFlashMode:FCPPlatformFlashModeTorch - withCompletion:^(FlutterError *_){ - }]; - [cam captureToFileWithCompletion:^(NSString *result, FlutterError *error) { - XCTAssertEqual(result, filePath); - [pathExpectation fulfill]; - }]; - }); - [self waitForExpectationsWithTimeout:30 handler:nil]; -} -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift new file mode 100644 index 00000000000..cef971cbe19 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift @@ -0,0 +1,194 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import AVFoundation +import XCTest + +@testable import camera_avfoundation + +/// Includes test cases related to photo capture operations for FLTCam class. +final class PhotoCaptureTests: XCTestCase { + private func createCam(with captureSessionQueue: DispatchQueue) -> FLTCam { + let configuration = FLTCreateTestCameraConfiguration() + configuration.captureSessionQueue = captureSessionQueue + return FLTCreateCamWithConfiguration(configuration) + } + + // FIXME Try to get rid of these ugly casts in this file. + + func testCaptureToFile_mustReportErrorToResultIfSavePhotoDelegateCompletionsWithError() { + let errorExpectation = expectation( + description: "Must send error to result if save photo delegate completes with error.") + let captureSessionQueue = DispatchQueue(label: "capture_session_queue") + dispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + let cam = createCam(with: captureSessionQueue) + let error = NSError(domain: "test", code: 0, userInfo: nil) + + let mockOutput = MockCapturePhotoOutput() + mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + let delegate = + cam.inProgressSavePhotoDelegates.object(forKey: settings!.uniqueID) as? FLTSavePhotoDelegate + // Completion runs on IO queue. + let ioQueue = DispatchQueue(label: "io_queue") + ioQueue.async { + delegate?.completionHandler(nil, error) + } + } + cam.capturePhotoOutput = mockOutput + + // `FLTCam::captureToFile` runs on capture session queue. + captureSessionQueue.async { + cam.captureToFile { result, error in + XCTAssertNil(result) + XCTAssertNotNil(error) + errorExpectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testCaptureToFile_mustReportPathToResultIfSavePhotoDelegateCompletionsWithPath() { + let pathExpectation = expectation( + description: "Must send file path to result if save photo delegate completes with file path.") + let captureSessionQueue = DispatchQueue(label: "capture_session_queue") + dispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + let cam = createCam(with: captureSessionQueue) + let filePath = "test" + + let mockOutput = MockCapturePhotoOutput() + mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + let delegate = + cam.inProgressSavePhotoDelegates.object(forKey: settings!.uniqueID) as? FLTSavePhotoDelegate + // Completion runs on IO queue. + let ioQueue = DispatchQueue(label: "io_queue") + ioQueue.async { + delegate?.completionHandler(filePath, nil) + } + } + cam.capturePhotoOutput = mockOutput + + // `FLTCam::captureToFile` runs on capture session queue. + captureSessionQueue.async { + cam.captureToFile { result, error in + XCTAssertEqual(result, filePath) + pathExpectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testCaptureToFile_mustReportFileExtensionWithHeifWhenHEVCIsAvailableAndFileFormatIsHEIF() { + let expectation = self.expectation( + description: "Test must set extension to heif if availablePhotoCodecTypes contains HEVC.") + + let captureSessionQueue = DispatchQueue(label: "capture_session_queue") + dispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + let cam = createCam(with: captureSessionQueue) + cam.setImageFileFormat(FCPPlatformImageFileFormat.heif) + + let mockOutput = MockCapturePhotoOutput() + mockOutput.availablePhotoCodecTypes = [AVVideoCodecType.hevc] + mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + let delegate = cam.inProgressSavePhotoDelegates[settings!.uniqueID] as? FLTSavePhotoDelegate + // Completion runs on IO queue. + let ioQueue = DispatchQueue(label: "io_queue") + ioQueue.async { + delegate?.completionHandler(delegate?.filePath, nil) + } + } + cam.capturePhotoOutput = mockOutput + + // `FLTCam::captureToFile` runs on capture session queue. + captureSessionQueue.async { + cam.captureToFile { filePath, error in + XCTAssertEqual((filePath! as NSString).pathExtension, "heif") + expectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testCaptureToFile_mustReportFileExtensionWithJpgWhenHEVCNotAvailableAndFileFormatIsHEIF() { + let expectation = self.expectation( + description: + "Test must set extension to jpg if availablePhotoCodecTypes does not contain HEVC.") + + let captureSessionQueue = DispatchQueue(label: "capture_session_queue") + dispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + let cam = createCam(with: captureSessionQueue) + cam.setImageFileFormat(FCPPlatformImageFileFormat.heif) + + let mockOutput = MockCapturePhotoOutput() + mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + let delegate = cam.inProgressSavePhotoDelegates[settings!.uniqueID] as? FLTSavePhotoDelegate + // Completion runs on IO queue. + let ioQueue = DispatchQueue(label: "io_queue") + ioQueue.async { + delegate?.completionHandler(delegate?.filePath, nil) + } + } + cam.capturePhotoOutput = mockOutput + + // `FLTCam::captureToFile` runs on capture session queue. + captureSessionQueue.async { + cam.captureToFile { filePath, error in + XCTAssertEqual((filePath! as NSString).pathExtension, "jpg") + expectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testCaptureToFile_handlesTorchMode() { + let pathExpectation = expectation( + description: "Must send file path to result if save photo delegate completes with file path.") + let setTorchExpectation = expectation( + description: "Should set torch mode to AVCaptureTorchModeOn.") + + let captureDeviceMock = MockCaptureDevice() + captureDeviceMock.hasTorch = true + captureDeviceMock.isTorchAvailable = true + captureDeviceMock.torchMode = .auto + captureDeviceMock.setTorchModeStub = { mode in + if mode == .on { + setTorchExpectation.fulfill() + } + } + + let captureSessionQueue = DispatchQueue(label: "capture_session_queue") + dispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + let configuration = FLTCreateTestCameraConfiguration() + configuration.captureSessionQueue = captureSessionQueue + configuration.captureDeviceFactory = { captureDeviceMock } + let cam = FLTCreateCamWithConfiguration(configuration) + + let filePath = "test" + + let mockOutput = MockCapturePhotoOutput() + mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + let delegate = cam.inProgressSavePhotoDelegates[settings!.uniqueID] as? FLTSavePhotoDelegate + // Completion runs on IO queue. + let ioQueue = DispatchQueue(label: "io_queue") + ioQueue.async { + delegate?.completionHandler(filePath, nil) + } + } + cam.capturePhotoOutput = mockOutput + + // `FLTCam::captureToFile` runs on capture session queue. + captureSessionQueue.async { + cam.setFlashMode(.torch) { _ in } + cam.captureToFile { result, error in + XCTAssertEqual(result, filePath) + pathExpectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index c6bc263b494..d3e404eedeb 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -7,6 +7,7 @@ #import "camera_avfoundation/CameraPlugin_Test.h" #import "camera_avfoundation/FLTCamConfiguration.h" #import "camera_avfoundation/FLTCam_Test.h" +#import "camera_avfoundation/FLTSavePhotoDelegate.h" #import "camera_avfoundation/FLTThreadSafeEventChannel.h" // Mocks, protocols. @@ -15,6 +16,7 @@ #import "MockCaptureConnection.h" #import "MockCaptureDevice.h" #import "MockCaptureDeviceFormat.h" +#import "MockCapturePhotoOutput.h" #import "MockCaptureSession.h" #import "MockDeviceOrientationProvider.h" #import "MockEventChannel.h" diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift index 96154213f98..6c89b979a80 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift @@ -63,11 +63,14 @@ private class FakeMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { /// Includes test cases related to sample buffer handling for FLTCam class. final class CameraSampleBufferTests: XCTestCase { - private func createCamera() -> (FLTCam, MockAssetWriter, MockAssetWriterInputPixelBufferAdaptor, MockAssetWriterInput, MockCaptureConnection) { + private func createCamera() -> ( + FLTCam, MockAssetWriter, MockAssetWriterInputPixelBufferAdaptor, MockAssetWriterInput, + MockCaptureConnection + ) { let assetWriter = MockAssetWriter() let adaptor = MockAssetWriterInputPixelBufferAdaptor() let input = MockAssetWriterInput() - + let configuration = FLTCreateTestCameraConfiguration() configuration.mediaSettings = FCPPlatformMediaSettings.make( with: .medium, framesPerSecond: nil, videoBitrate: nil, audioBitrate: nil, enableAudio: true) @@ -80,7 +83,10 @@ final class CameraSampleBufferTests: XCTestCase { return adaptor } - return (FLTCreateCamWithConfiguration(configuration), assetWriter, adaptor, input, MockCaptureConnection()) + return ( + FLTCreateCamWithConfiguration(configuration), assetWriter, adaptor, input, + MockCaptureConnection() + ) } func testSampleBufferCallbackQueueMustBeCaptureSessionQueue() { @@ -127,7 +133,7 @@ final class CameraSampleBufferTests: XCTestCase { func testDidOutputSampleBufferIgnoreAudioSamplesBeforeVideoSamples() { let (camera, writerMock, adaptorMock, inputMock, connectionMock) = createCamera() - + var status = AVAssetWriter.Status.unknown writerMock.startWritingStub = { status = .writing From d7c6113dc5a42d27942662834d947f9e831113df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Tue, 18 Feb 2025 16:18:43 +0100 Subject: [PATCH 26/40] Move Objective-C files up in XCode --- .../example/ios/Runner.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index e85cbd867c1..58d8f7035d6 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -175,6 +175,8 @@ 03BB766C2665316900CE5A93 /* Info.plist */, E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */, E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */, + 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */, + 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */, 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */, 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */, 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */, @@ -189,8 +191,6 @@ 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */, 977A25232D5A511600931E34 /* CameraPermissionTests.swift */, 97C0FFAD2D5E023200A36284 /* SavePhotoDelegateTests.swift */, - 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */, - 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */, 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */, 978D90B32D5F630300CD817E /* StreamingTests.swift */, 97922B0C2D6380C300A9B4CF /* SampleBufferTests.swift */, From 6d20234f60459e9bd52bdf3dde5970a7c037d31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 20 Feb 2025 10:32:51 +0100 Subject: [PATCH 27/40] Remove "any" keyword --- .../example/ios/RunnerTests/SampleBufferTests.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift index 6c89b979a80..28e6a600dac 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift @@ -22,19 +22,19 @@ private class FakeMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { // No-op. } - override func beginConfiguration(for videoCaptureSession: any FLTCaptureSession) { + override func beginConfiguration(for videoCaptureSession: FLTCaptureSession) { // No-op. } - override func commitConfiguration(for videoCaptureSession: any FLTCaptureSession) { + override func commitConfiguration(for videoCaptureSession: FLTCaptureSession) { // No-op. } - override func setMinFrameDuration(_ duration: CMTime, on captureDevice: any FLTCaptureDevice) { + override func setMinFrameDuration(_ duration: CMTime, on captureDevice: FLTCaptureDevice) { // No-op. } - override func setMaxFrameDuration(_ duration: CMTime, on captureDevice: any FLTCaptureDevice) { + override func setMaxFrameDuration(_ duration: CMTime, on captureDevice: FLTCaptureDevice) { // No-op. } @@ -50,7 +50,7 @@ private class FakeMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { return inputMock } - override func addInput(_ writerInput: any FLTAssetWriterInput, to writer: any FLTAssetWriter) { + override func addInput(_ writerInput: FLTAssetWriterInput, to writer: FLTAssetWriter) { // No-op. } From 882904a5c0ab922e381cfb48ecedb4048dd45e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 20 Feb 2025 10:46:00 +0100 Subject: [PATCH 28/40] Remove resolved fixme comment --- .../example/ios/RunnerTests/PhotoCaptureTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift index cef971cbe19..c2fb1834006 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift @@ -15,8 +15,6 @@ final class PhotoCaptureTests: XCTestCase { return FLTCreateCamWithConfiguration(configuration) } - // FIXME Try to get rid of these ugly casts in this file. - func testCaptureToFile_mustReportErrorToResultIfSavePhotoDelegateCompletionsWithError() { let errorExpectation = expectation( description: "Must send error to result if save photo delegate completes with error.") From 7b7a8816f8903e5121934e26f3deeafc055bf42b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 20 Feb 2025 11:39:57 +0100 Subject: [PATCH 29/40] Clean up whitespacing --- .../example/ios/RunnerTests/PhotoCaptureTests.swift | 1 - .../example/ios/RunnerTests/SampleBufferTests.swift | 1 - .../example/ios/RunnerTests/ThreadSafeEventChannelTests.swift | 2 -- 3 files changed, 4 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift index c2fb1834006..09b903d5a62 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift @@ -166,7 +166,6 @@ final class PhotoCaptureTests: XCTestCase { let cam = FLTCreateCamWithConfiguration(configuration) let filePath = "test" - let mockOutput = MockCapturePhotoOutput() mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in let delegate = cam.inProgressSavePhotoDelegates[settings!.uniqueID] as? FLTSavePhotoDelegate diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift index 28e6a600dac..70451dfad9b 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift @@ -133,7 +133,6 @@ final class CameraSampleBufferTests: XCTestCase { func testDidOutputSampleBufferIgnoreAudioSamplesBeforeVideoSamples() { let (camera, writerMock, adaptorMock, inputMock, connectionMock) = createCamera() - var status = AVAssetWriter.Status.unknown writerMock.startWritingStub = { status = .writing diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift index 8728405db7c..26ffe68d88a 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift @@ -29,7 +29,6 @@ final class ThreadSafeEventChannelTests: XCTestCase { mainThreadExpectation.fulfill() } } - threadSafeEventChannel.setStreamHandler(nil) { if Thread.isMainThread { mainThreadCompletionExpectation.fulfill() @@ -53,7 +52,6 @@ final class ThreadSafeEventChannelTests: XCTestCase { mainThreadExpectation.fulfill() } } - DispatchQueue.global(qos: .default).async { threadSafeEventChannel.setStreamHandler(nil) { if Thread.isMainThread { From f6b743f8326990e97d1ae90e5956c91cefcce1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 20 Feb 2025 12:44:36 +0100 Subject: [PATCH 30/40] Migrate MockWritableData to Swift --- .../ios/Runner.xcodeproj/project.pbxproj | 12 +++++------- .../ios/RunnerTests/Mocks/MockWritableData.h | 16 ---------------- .../ios/RunnerTests/Mocks/MockWritableData.m | 18 ------------------ .../RunnerTests/Mocks/MockWritableData.swift | 16 ++++++++++++++++ .../RunnerTests/RunnerTests-Bridging-Header.h | 2 +- .../RunnerTests/SavePhotoDelegateTests.swift | 14 ++++---------- 6 files changed, 26 insertions(+), 52 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.h delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.m create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index 403053151f1..f5200b0cf22 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -20,11 +20,11 @@ 7F8FD22F2D4D0B88001AF2C1 /* MockFlutterBinaryMessenger.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F8FD22E2D4D0B88001AF2C1 /* MockFlutterBinaryMessenger.m */; }; 7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FCEDD342D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m */; }; 7FCEDD362D43C2B900EA1CA8 /* MockCaptureDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FCEDD322D43C2B900EA1CA8 /* MockCaptureDevice.m */; }; - 7FD582122D579650003B1200 /* MockWritableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582112D579650003B1200 /* MockWritableData.m */; }; 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */; }; 7FD582272D57C020003B1200 /* MockAssetWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582262D57C020003B1200 /* MockAssetWriter.m */; }; 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */; }; 7FD83D2B2D5BA65B00F4DB7C /* MockCaptureConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD83D2A2D5BA65B00F4DB7C /* MockCaptureConnection.m */; }; + 970ADABE2D6740A900EFDCD9 /* MockWritableData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970ADABD2D6740A900EFDCD9 /* MockWritableData.swift */; }; 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */; }; 972CA92D2D5A28C4004B846F /* QueueUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */; }; 972CA9312D5A366C004B846F /* CameraExposureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA9302D5A366C004B846F /* CameraExposureTests.swift */; }; @@ -100,8 +100,6 @@ 7FCEDD322D43C2B900EA1CA8 /* MockCaptureDevice.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureDevice.m; sourceTree = ""; }; 7FCEDD332D43C2B900EA1CA8 /* MockDeviceOrientationProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockDeviceOrientationProvider.h; sourceTree = ""; }; 7FCEDD342D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockDeviceOrientationProvider.m; sourceTree = ""; }; - 7FD582112D579650003B1200 /* MockWritableData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockWritableData.m; sourceTree = ""; }; - 7FD582132D57965A003B1200 /* MockWritableData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockWritableData.h; sourceTree = ""; }; 7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCapturePhotoOutput.m; sourceTree = ""; }; 7FD582212D579ED9003B1200 /* MockCapturePhotoOutput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCapturePhotoOutput.h; sourceTree = ""; }; 7FD582262D57C020003B1200 /* MockAssetWriter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockAssetWriter.m; sourceTree = ""; }; @@ -110,6 +108,7 @@ 7FD582362D57D989003B1200 /* MockCaptureDeviceFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureDeviceFormat.h; sourceTree = ""; }; 7FD83D292D5BA49100F4DB7C /* MockCaptureConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureConnection.h; sourceTree = ""; }; 7FD83D2A2D5BA65B00F4DB7C /* MockCaptureConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureConnection.m; sourceTree = ""; }; + 970ADABD2D6740A900EFDCD9 /* MockWritableData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockWritableData.swift; sourceTree = ""; }; 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPropertiesTests.swift; sourceTree = ""; }; 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueUtilsTests.swift; sourceTree = ""; }; 972CA9302D5A366C004B846F /* CameraExposureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraExposureTests.swift; sourceTree = ""; }; @@ -235,8 +234,7 @@ 7F29EB212D269ED500740257 /* MockEventChannel.m */, 7F29EB3E2D281C5800740257 /* MockCaptureSession.h */, 7F29EB402D281C7E00740257 /* MockCaptureSession.m */, - 7FD582112D579650003B1200 /* MockWritableData.m */, - 7FD582132D57965A003B1200 /* MockWritableData.h */, + 970ADABD2D6740A900EFDCD9 /* MockWritableData.swift */, ); path = Mocks; sourceTree = ""; @@ -530,6 +528,7 @@ 979B3DFB2D5B6BC7009BDE1A /* ExceptionCatcher.m in Sources */, 7FD83D2B2D5BA65B00F4DB7C /* MockCaptureConnection.m in Sources */, 977A25242D5A511600931E34 /* CameraPermissionTests.swift in Sources */, + 970ADABE2D6740A900EFDCD9 /* MockWritableData.swift in Sources */, 7F8FD2292D4BFABF001AF2C1 /* MockGlobalEventApi.m in Sources */, 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */, 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */, @@ -551,7 +550,6 @@ 977A25222D5A49EC00931E34 /* CameraFocusTests.swift in Sources */, 978D90B42D5F630300CD817E /* StreamingTests.swift in Sources */, 7F29EB412D281C7E00740257 /* MockCaptureSession.m in Sources */, - 7FD582122D579650003B1200 /* MockWritableData.m in Sources */, 7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */, 977CAC9F2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift in Sources */, 7FCEDD362D43C2B900EA1CA8 /* MockCaptureDevice.m in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.h deleted file mode 100644 index 746765050ed..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -@import AVFoundation; - -/// A mock implementation of `FLTWritableData` that allows injecting a custom implementation -/// for writing to a file. -@interface MockWritableData : NSObject - -/// A stub that replaces the default implementation of `writeToFile:options:error:`. -@property(nonatomic, copy) BOOL (^writeToFileStub) - (NSString *path, NSDataWritingOptions options, NSError **error); - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.m deleted file mode 100644 index 8b0a354a8cb..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.m +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "MockWritableData.h" - -@implementation MockWritableData - -- (BOOL)writeToFile:(NSString *)path - options:(NSDataWritingOptions)writeOptionsMask - error:(NSError **)errorPtr { - if (self.writeToFileStub) { - return _writeToFileStub(path, writeOptionsMask, errorPtr); - } - return YES; -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift new file mode 100644 index 00000000000..43563a888fa --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// A mock implementation of `FLTWritableData` that allows injecting a custom implementation +/// for writing to a file. +class MockWritableData: NSObject, FLTWritableData { + /// A stub that replaces the default implementation of `write(toFile,options).`. + var writeToFileStub: ((String, NSData.WritingOptions) throws -> Void)? + + func write(toFile path: String, options writeOptionsMask: NSData.WritingOptions) throws { + if let stub = self.writeToFileStub { + return try stub(path, writeOptionsMask) + } + } +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index d3e404eedeb..c57e8632cc7 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -23,7 +23,7 @@ #import "MockFlutterBinaryMessenger.h" #import "MockFlutterTextureRegistry.h" #import "MockGlobalEventApi.h" -#import "MockWritableData.h" +//#import "MockWritableData.h" // Utils. #import "CameraTestUtils.h" diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift index 02a927557aa..0e92239f9d7 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift @@ -37,12 +37,9 @@ final class SavePhotoDelegateTests: XCTestCase { } let mockWritableData = MockWritableData() - mockWritableData.writeToFileStub = { path, options, error in - // TODO(FirentisTFW) Throw an error instead when migrating FLTWritableData to Swift - error?.pointee = ioError - return false + mockWritableData.writeToFileStub = { path, options in + throw ioError } - delegate.handlePhotoCaptureResult(error: nil) { mockWritableData } waitForExpectations(timeout: 30, handler: nil) @@ -60,9 +57,7 @@ final class SavePhotoDelegateTests: XCTestCase { } let mockWritableData = MockWritableData() - mockWritableData.writeToFileStub = { path, options, error in - return true - } + mockWritableData.writeToFileStub = { path, options in } delegate.handlePhotoCaptureResult(error: nil) { mockWritableData } @@ -80,11 +75,10 @@ final class SavePhotoDelegateTests: XCTestCase { ioQueue.setSpecific(key: ioQueueSpecific, value: ()) let mockWritableData = MockWritableData() - mockWritableData.writeToFileStub = { path, options, error in + mockWritableData.writeToFileStub = { path, options in if DispatchQueue.getSpecific(key: ioQueueSpecific) != nil { writeFileQueueExpectation.fulfill() } - return true } let filePath = "test" From 808f21b87de4456f50c090eb7518ff0e276d1026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 20 Feb 2025 14:31:37 +0100 Subject: [PATCH 31/40] Migrate MockEventChannel to Swift --- .../ios/Runner.xcodeproj/project.pbxproj | 10 ++++----- .../ios/RunnerTests/Mocks/MockEventChannel.h | 21 ------------------- .../ios/RunnerTests/Mocks/MockEventChannel.m | 15 ------------- .../RunnerTests/Mocks/MockEventChannel.swift | 9 ++++++++ .../RunnerTests/Mocks/MockWritableData.swift | 2 +- .../RunnerTests/RunnerTests-Bridging-Header.h | 2 -- .../ThreadSafeEventChannelTests.swift | 8 ------- 7 files changed, 14 insertions(+), 53 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.h delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.m create mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index f5200b0cf22..575ae7828f2 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -12,7 +12,6 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 54D650172516862D30686934 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ECAF63F924EFA2D68883BA85 /* libPods-Runner.a */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - 7F29EB222D269ED500740257 /* MockEventChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F29EB212D269ED500740257 /* MockEventChannel.m */; }; 7F29EB292D26A59000740257 /* MockCameraDeviceDiscoverer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F29EB282D26A59000740257 /* MockCameraDeviceDiscoverer.m */; }; 7F29EB412D281C7E00740257 /* MockCaptureSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F29EB402D281C7E00740257 /* MockCaptureSession.m */; }; 7F8FD2292D4BFABF001AF2C1 /* MockGlobalEventApi.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F8FD2282D4BFABF001AF2C1 /* MockGlobalEventApi.m */; }; @@ -25,6 +24,7 @@ 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */; }; 7FD83D2B2D5BA65B00F4DB7C /* MockCaptureConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD83D2A2D5BA65B00F4DB7C /* MockCaptureConnection.m */; }; 970ADABE2D6740A900EFDCD9 /* MockWritableData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970ADABD2D6740A900EFDCD9 /* MockWritableData.swift */; }; + 970ADAC02D6764CC00EFDCD9 /* MockEventChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970ADABF2D6764CC00EFDCD9 /* MockEventChannel.swift */; }; 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */; }; 972CA92D2D5A28C4004B846F /* QueueUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */; }; 972CA9312D5A366C004B846F /* CameraExposureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA9302D5A366C004B846F /* CameraExposureTests.swift */; }; @@ -84,8 +84,6 @@ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 7F29EB202D269E4300740257 /* MockEventChannel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockEventChannel.h; sourceTree = ""; }; - 7F29EB212D269ED500740257 /* MockEventChannel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockEventChannel.m; sourceTree = ""; }; 7F29EB272D26A55300740257 /* MockCameraDeviceDiscoverer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCameraDeviceDiscoverer.h; sourceTree = ""; }; 7F29EB282D26A59000740257 /* MockCameraDeviceDiscoverer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCameraDeviceDiscoverer.m; sourceTree = ""; }; 7F29EB3E2D281C5800740257 /* MockCaptureSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureSession.h; sourceTree = ""; }; @@ -109,6 +107,7 @@ 7FD83D292D5BA49100F4DB7C /* MockCaptureConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureConnection.h; sourceTree = ""; }; 7FD83D2A2D5BA65B00F4DB7C /* MockCaptureConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureConnection.m; sourceTree = ""; }; 970ADABD2D6740A900EFDCD9 /* MockWritableData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockWritableData.swift; sourceTree = ""; }; + 970ADABF2D6764CC00EFDCD9 /* MockEventChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockEventChannel.swift; sourceTree = ""; }; 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPropertiesTests.swift; sourceTree = ""; }; 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueUtilsTests.swift; sourceTree = ""; }; 972CA9302D5A366C004B846F /* CameraExposureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraExposureTests.swift; sourceTree = ""; }; @@ -230,11 +229,10 @@ 7FCEDD342D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m */, 7F29EB282D26A59000740257 /* MockCameraDeviceDiscoverer.m */, 7F29EB272D26A55300740257 /* MockCameraDeviceDiscoverer.h */, - 7F29EB202D269E4300740257 /* MockEventChannel.h */, - 7F29EB212D269ED500740257 /* MockEventChannel.m */, 7F29EB3E2D281C5800740257 /* MockCaptureSession.h */, 7F29EB402D281C7E00740257 /* MockCaptureSession.m */, 970ADABD2D6740A900EFDCD9 /* MockWritableData.swift */, + 970ADABF2D6764CC00EFDCD9 /* MockEventChannel.swift */, ); path = Mocks; sourceTree = ""; @@ -532,7 +530,6 @@ 7F8FD2292D4BFABF001AF2C1 /* MockGlobalEventApi.m in Sources */, 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */, 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */, - 7F29EB222D269ED500740257 /* MockEventChannel.m in Sources */, 7F8FD22F2D4D0B88001AF2C1 /* MockFlutterBinaryMessenger.m in Sources */, 97922B0D2D6380C300A9B4CF /* SampleBufferTests.swift in Sources */, 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */, @@ -541,6 +538,7 @@ 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */, 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */, 97DB234D2D566D0700CEFE66 /* CameraPreviewPauseTests.swift in Sources */, + 970ADAC02D6764CC00EFDCD9 /* MockEventChannel.swift in Sources */, 977A25202D5A439300931E34 /* AvailableCamerasTests.swift in Sources */, 972CA9312D5A366C004B846F /* CameraExposureTests.swift in Sources */, 7F29EB292D26A59000740257 /* MockCameraDeviceDiscoverer.m in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.h deleted file mode 100644 index d92adf35c05..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import Flutter; - -NS_ASSUME_NONNULL_BEGIN - -/// A mock implementation of `FLTEventChannel` that allows injecting a custom stream handler. -@interface MockEventChannel : NSObject - -/// Overrides the default implementation of setting the stream handler. -@property(nonatomic, copy) void (^setStreamHandlerStub)(NSObject *); - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.m deleted file mode 100644 index 7190cfb1771..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.m +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "MockEventChannel.h" - -@implementation MockEventChannel - -- (void)setStreamHandler:(NSObject *)handler { - if (self.setStreamHandlerStub) { - self.setStreamHandlerStub(handler); - } -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift new file mode 100644 index 00000000000..962f6e60ce4 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift @@ -0,0 +1,9 @@ +/// A mock implementation of `FLTEventChannel` that allows injecting a custom implementation +/// for setting a stream handler.. +final class MockEventChannel: NSObject, FLTEventChannel { + var setStreamHandlerStub: ((FlutterStreamHandler?) -> Void)? + + func setStreamHandler(_ handler: (FlutterStreamHandler & NSObjectProtocol)?) { + setStreamHandlerStub?(handler) + } +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift index 43563a888fa..4099680603f 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift @@ -4,7 +4,7 @@ /// A mock implementation of `FLTWritableData` that allows injecting a custom implementation /// for writing to a file. -class MockWritableData: NSObject, FLTWritableData { +final class MockWritableData: NSObject, FLTWritableData { /// A stub that replaces the default implementation of `write(toFile,options).`. var writeToFileStub: ((String, NSData.WritingOptions) throws -> Void)? diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index c57e8632cc7..bf3b2bd9e3c 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -19,11 +19,9 @@ #import "MockCapturePhotoOutput.h" #import "MockCaptureSession.h" #import "MockDeviceOrientationProvider.h" -#import "MockEventChannel.h" #import "MockFlutterBinaryMessenger.h" #import "MockFlutterTextureRegistry.h" #import "MockGlobalEventApi.h" -//#import "MockWritableData.h" // Utils. #import "CameraTestUtils.h" diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift index 26ffe68d88a..5e0790d22f3 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift @@ -6,14 +6,6 @@ import XCTest @testable import camera_avfoundation -private final class MockEventChannel: NSObject, FLTEventChannel { - var setStreamHandlerStub: ((FlutterStreamHandler?) -> Void)? - - func setStreamHandler(_ handler: (FlutterStreamHandler & NSObjectProtocol)?) { - setStreamHandlerStub?(handler) - } -} - final class ThreadSafeEventChannelTests: XCTestCase { func testSetStreamHandler_shouldStayOnMainThreadIfCalledFromMainThread() { let mockEventChannel = MockEventChannel() From 21fe3ac178a54ad571a62b63a6357510668c7769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 20 Feb 2025 15:41:09 +0100 Subject: [PATCH 32/40] Add missing license --- .../example/ios/RunnerTests/Mocks/MockEventChannel.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift index 962f6e60ce4..c5fd262c2fa 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift @@ -1,3 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + /// A mock implementation of `FLTEventChannel` that allows injecting a custom implementation /// for setting a stream handler.. final class MockEventChannel: NSObject, FLTEventChannel { From b97d86ac942604dc4641201138fad431be02fb0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 20 Feb 2025 21:14:39 +0100 Subject: [PATCH 33/40] Clean up mocks code --- .../example/ios/RunnerTests/Mocks/MockEventChannel.swift | 2 +- .../example/ios/RunnerTests/Mocks/MockWritableData.swift | 1 - .../example/ios/RunnerTests/SavePhotoDelegateTests.swift | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift index c5fd262c2fa..84c6a40d30e 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift @@ -3,7 +3,7 @@ // found in the LICENSE file. /// A mock implementation of `FLTEventChannel` that allows injecting a custom implementation -/// for setting a stream handler.. +/// for setting a stream handler. final class MockEventChannel: NSObject, FLTEventChannel { var setStreamHandlerStub: ((FlutterStreamHandler?) -> Void)? diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift index 4099680603f..352fe7efbea 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift @@ -5,7 +5,6 @@ /// A mock implementation of `FLTWritableData` that allows injecting a custom implementation /// for writing to a file. final class MockWritableData: NSObject, FLTWritableData { - /// A stub that replaces the default implementation of `write(toFile,options).`. var writeToFileStub: ((String, NSData.WritingOptions) throws -> Void)? func write(toFile path: String, options writeOptionsMask: NSData.WritingOptions) throws { diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift index 0e92239f9d7..0e4c2e00441 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift @@ -40,6 +40,7 @@ final class SavePhotoDelegateTests: XCTestCase { mockWritableData.writeToFileStub = { path, options in throw ioError } + delegate.handlePhotoCaptureResult(error: nil) { mockWritableData } waitForExpectations(timeout: 30, handler: nil) From 50358ef48f227dfa977fdfa94060f1a19cfb7658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Sun, 23 Feb 2025 22:08:08 +0100 Subject: [PATCH 34/40] Use prefix for Objecive-C code --- .../example/ios/RunnerTests/CameraTestUtils.h | 2 +- .../example/ios/RunnerTests/CameraTestUtils.m | 2 +- .../example/ios/RunnerTests/PhotoCaptureTests.swift | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index 0844b292af5..aae88cfa738 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -35,6 +35,6 @@ extern CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void); /// In Swift, the API for settinng key-value pairs on a queue is different, so Swift tests /// need to call this method to set the key-value pair on the queue in a way that's /// compatible withn the existing Objective-C code. -extern void dispatchQueueSetSpecific(dispatch_queue_t queue, const void *key); +extern void FLTdispatchQueueSetSpecific(dispatch_queue_t queue, const void *key); NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index 2b66cde9cce..1e2f5f6bde0 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -128,6 +128,6 @@ CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void) { return sampleBuffer; } -void dispatchQueueSetSpecific(dispatch_queue_t queue, const void *key) { +void FLTdispatchQueueSetSpecific(dispatch_queue_t queue, const void *key) { dispatch_queue_set_specific(queue, key, (void *)key, NULL); } diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift index 09b903d5a62..07325dffa44 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift @@ -19,7 +19,7 @@ final class PhotoCaptureTests: XCTestCase { let errorExpectation = expectation( description: "Must send error to result if save photo delegate completes with error.") let captureSessionQueue = DispatchQueue(label: "capture_session_queue") - dispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + FLTdispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) let cam = createCam(with: captureSessionQueue) let error = NSError(domain: "test", code: 0, userInfo: nil) @@ -51,7 +51,7 @@ final class PhotoCaptureTests: XCTestCase { let pathExpectation = expectation( description: "Must send file path to result if save photo delegate completes with file path.") let captureSessionQueue = DispatchQueue(label: "capture_session_queue") - dispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + FLTdispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) let cam = createCam(with: captureSessionQueue) let filePath = "test" @@ -83,7 +83,7 @@ final class PhotoCaptureTests: XCTestCase { description: "Test must set extension to heif if availablePhotoCodecTypes contains HEVC.") let captureSessionQueue = DispatchQueue(label: "capture_session_queue") - dispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + FLTdispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) let cam = createCam(with: captureSessionQueue) cam.setImageFileFormat(FCPPlatformImageFileFormat.heif) @@ -116,7 +116,7 @@ final class PhotoCaptureTests: XCTestCase { "Test must set extension to jpg if availablePhotoCodecTypes does not contain HEVC.") let captureSessionQueue = DispatchQueue(label: "capture_session_queue") - dispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + FLTdispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) let cam = createCam(with: captureSessionQueue) cam.setImageFileFormat(FCPPlatformImageFileFormat.heif) @@ -159,7 +159,7 @@ final class PhotoCaptureTests: XCTestCase { } let captureSessionQueue = DispatchQueue(label: "capture_session_queue") - dispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + FLTdispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) let configuration = FLTCreateTestCameraConfiguration() configuration.captureSessionQueue = captureSessionQueue configuration.captureDeviceFactory = { captureDeviceMock } From 9f2b3e73502d68b621cdf0ec3ca84c51828e4e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Sun, 23 Feb 2025 22:09:51 +0100 Subject: [PATCH 35/40] Remove redundant return statement --- .../example/ios/RunnerTests/Mocks/MockWritableData.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift index 352fe7efbea..912c77cb48e 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift @@ -9,7 +9,7 @@ final class MockWritableData: NSObject, FLTWritableData { func write(toFile path: String, options writeOptionsMask: NSData.WritingOptions) throws { if let stub = self.writeToFileStub { - return try stub(path, writeOptionsMask) + try stub(path, writeOptionsMask) } } } From a4741a35f921b3190631f7e40893405ec5ccda8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Sun, 23 Feb 2025 22:26:42 +0100 Subject: [PATCH 36/40] Put every argument in a separate line for long lists --- .../example/ios/RunnerTests/SampleBufferTests.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift index 70451dfad9b..686f437a93c 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift @@ -64,7 +64,10 @@ private class FakeMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { /// Includes test cases related to sample buffer handling for FLTCam class. final class CameraSampleBufferTests: XCTestCase { private func createCamera() -> ( - FLTCam, MockAssetWriter, MockAssetWriterInputPixelBufferAdaptor, MockAssetWriterInput, + FLTCam, + MockAssetWriter, + MockAssetWriterInputPixelBufferAdaptor, + MockAssetWriterInput, MockCaptureConnection ) { let assetWriter = MockAssetWriter() @@ -73,7 +76,11 @@ final class CameraSampleBufferTests: XCTestCase { let configuration = FLTCreateTestCameraConfiguration() configuration.mediaSettings = FCPPlatformMediaSettings.make( - with: .medium, framesPerSecond: nil, videoBitrate: nil, audioBitrate: nil, enableAudio: true) + with: .medium, + framesPerSecond: nil, + videoBitrate: nil, + audioBitrate: nil, + enableAudio: true) configuration.mediaSettingsWrapper = FakeMediaSettingsAVWrapper(inputMock: input) configuration.assetWriterFactory = { url, fileType, error in From 24d0175270afc3c42bd78bae815bd199c0495497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Sun, 23 Feb 2025 22:48:29 +0100 Subject: [PATCH 37/40] Do not use ! in tests code --- .../ios/RunnerTests/PhotoCaptureTests.swift | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift index 07325dffa44..b4990d7dcaa 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift @@ -25,8 +25,13 @@ final class PhotoCaptureTests: XCTestCase { let mockOutput = MockCapturePhotoOutput() mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + guard let unwrappedSettings = try? XCTUnwrap(settings, "Settings is unexpectedly nil") else { + XCTFail("Settings is unexpectedly nil") + return + } let delegate = - cam.inProgressSavePhotoDelegates.object(forKey: settings!.uniqueID) as? FLTSavePhotoDelegate + cam.inProgressSavePhotoDelegates.object(forKey: unwrappedSettings.uniqueID) + as? FLTSavePhotoDelegate // Completion runs on IO queue. let ioQueue = DispatchQueue(label: "io_queue") ioQueue.async { @@ -57,8 +62,13 @@ final class PhotoCaptureTests: XCTestCase { let mockOutput = MockCapturePhotoOutput() mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + guard let unwrappedSettings = try? XCTUnwrap(settings, "Settings is unexpectedly nil") else { + XCTFail("Settings is unexpectedly nil") + return + } let delegate = - cam.inProgressSavePhotoDelegates.object(forKey: settings!.uniqueID) as? FLTSavePhotoDelegate + cam.inProgressSavePhotoDelegates.object(forKey: unwrappedSettings.uniqueID) + as? FLTSavePhotoDelegate // Completion runs on IO queue. let ioQueue = DispatchQueue(label: "io_queue") ioQueue.async { @@ -90,7 +100,13 @@ final class PhotoCaptureTests: XCTestCase { let mockOutput = MockCapturePhotoOutput() mockOutput.availablePhotoCodecTypes = [AVVideoCodecType.hevc] mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in - let delegate = cam.inProgressSavePhotoDelegates[settings!.uniqueID] as? FLTSavePhotoDelegate + guard let unwrappedSettings = try? XCTUnwrap(settings, "Settings is unexpectedly nil") else { + XCTFail("Settings is unexpectedly nil") + return + } + let delegate = + cam.inProgressSavePhotoDelegates.object(forKey: unwrappedSettings.uniqueID) + as? FLTSavePhotoDelegate // Completion runs on IO queue. let ioQueue = DispatchQueue(label: "io_queue") ioQueue.async { @@ -122,7 +138,13 @@ final class PhotoCaptureTests: XCTestCase { let mockOutput = MockCapturePhotoOutput() mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in - let delegate = cam.inProgressSavePhotoDelegates[settings!.uniqueID] as? FLTSavePhotoDelegate + guard let unwrappedSettings = try? XCTUnwrap(settings, "Settings is unexpectedly nil") else { + XCTFail("Settings is unexpectedly nil") + return + } + let delegate = + cam.inProgressSavePhotoDelegates.object(forKey: unwrappedSettings.uniqueID) + as? FLTSavePhotoDelegate // Completion runs on IO queue. let ioQueue = DispatchQueue(label: "io_queue") ioQueue.async { @@ -168,7 +190,13 @@ final class PhotoCaptureTests: XCTestCase { let filePath = "test" let mockOutput = MockCapturePhotoOutput() mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in - let delegate = cam.inProgressSavePhotoDelegates[settings!.uniqueID] as? FLTSavePhotoDelegate + guard let unwrappedSettings = try? XCTUnwrap(settings, "Settings is unexpectedly nil") else { + XCTFail("Settings is unexpectedly nil") + return + } + let delegate = + cam.inProgressSavePhotoDelegates.object(forKey: unwrappedSettings.uniqueID) + as? FLTSavePhotoDelegate // Completion runs on IO queue. let ioQueue = DispatchQueue(label: "io_queue") ioQueue.async { From 887e487517fc1b45005e1bb24d998d5b3680aa95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 27 Feb 2025 11:34:08 +0100 Subject: [PATCH 38/40] Add a todo to verify if properties need to be nullable --- .../example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h index 7a43852e3b5..c383757ff30 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h @@ -15,6 +15,7 @@ @property(nonatomic, assign) BOOL highResolutionCaptureEnabled; @property(nonatomic, strong) NSArray *supportedFlashModes; +// TODO(RobertOdrowaz): Verify if settings and delegate need to be nullable when migrating to Swift. // Stub that is called when the corresponding public method is called. @property(nonatomic, copy) void (^capturePhotoWithSettingsStub) (AVCapturePhotoSettings *, NSObject *); From d189ba2309695591c9864f2f9211f4798eb65e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 27 Feb 2025 11:38:11 +0100 Subject: [PATCH 39/40] Move todo from mock to protocol --- .../example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h | 1 - .../include/camera_avfoundation/FLTCapturePhotoOutput.h | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h index c383757ff30..7a43852e3b5 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h @@ -15,7 +15,6 @@ @property(nonatomic, assign) BOOL highResolutionCaptureEnabled; @property(nonatomic, strong) NSArray *supportedFlashModes; -// TODO(RobertOdrowaz): Verify if settings and delegate need to be nullable when migrating to Swift. // Stub that is called when the corresponding public method is called. @property(nonatomic, copy) void (^capturePhotoWithSettingsStub) (AVCapturePhotoSettings *, NSObject *); diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCapturePhotoOutput.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCapturePhotoOutput.h index f569f739c52..a4e28b60139 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCapturePhotoOutput.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCapturePhotoOutput.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, assign) BOOL highResolutionCaptureEnabled; @property(nonatomic, readonly) NSArray *supportedFlashModes; +// TODO(RobertOdrowaz): Verify if settings and delegate need to be nullable when migrating to Swift. - (void)capturePhotoWithSettings:(AVCapturePhotoSettings *)settings delegate:(NSObject *)delegate; - (nullable AVCaptureConnection *)connectionWithMediaType:(AVMediaType)mediaType; From 18f171452f71b4009e63a40c2c7286105821431a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Thu, 27 Feb 2025 11:49:37 +0100 Subject: [PATCH 40/40] Remove todo and make parameters non-nullable --- .../Mocks/MockCapturePhotoOutput.h | 4 +++ .../ios/RunnerTests/PhotoCaptureTests.swift | 30 ++++--------------- .../FLTCapturePhotoOutput.h | 1 - 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h index 7a43852e3b5..403c9582f32 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h @@ -5,6 +5,8 @@ @import camera_avfoundation; @import AVFoundation; +NS_ASSUME_NONNULL_BEGIN + /// Mock implementation of `FLTCapturePhotoOutput` protocol which allows injecting a custom /// implementation. @interface MockCapturePhotoOutput : NSObject @@ -20,3 +22,5 @@ (AVCapturePhotoSettings *, NSObject *); @end + +NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift index b4990d7dcaa..fe26009bc2e 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift @@ -25,12 +25,8 @@ final class PhotoCaptureTests: XCTestCase { let mockOutput = MockCapturePhotoOutput() mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in - guard let unwrappedSettings = try? XCTUnwrap(settings, "Settings is unexpectedly nil") else { - XCTFail("Settings is unexpectedly nil") - return - } let delegate = - cam.inProgressSavePhotoDelegates.object(forKey: unwrappedSettings.uniqueID) + cam.inProgressSavePhotoDelegates.object(forKey: settings.uniqueID) as? FLTSavePhotoDelegate // Completion runs on IO queue. let ioQueue = DispatchQueue(label: "io_queue") @@ -62,12 +58,8 @@ final class PhotoCaptureTests: XCTestCase { let mockOutput = MockCapturePhotoOutput() mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in - guard let unwrappedSettings = try? XCTUnwrap(settings, "Settings is unexpectedly nil") else { - XCTFail("Settings is unexpectedly nil") - return - } let delegate = - cam.inProgressSavePhotoDelegates.object(forKey: unwrappedSettings.uniqueID) + cam.inProgressSavePhotoDelegates.object(forKey: settings.uniqueID) as? FLTSavePhotoDelegate // Completion runs on IO queue. let ioQueue = DispatchQueue(label: "io_queue") @@ -100,12 +92,8 @@ final class PhotoCaptureTests: XCTestCase { let mockOutput = MockCapturePhotoOutput() mockOutput.availablePhotoCodecTypes = [AVVideoCodecType.hevc] mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in - guard let unwrappedSettings = try? XCTUnwrap(settings, "Settings is unexpectedly nil") else { - XCTFail("Settings is unexpectedly nil") - return - } let delegate = - cam.inProgressSavePhotoDelegates.object(forKey: unwrappedSettings.uniqueID) + cam.inProgressSavePhotoDelegates.object(forKey: settings.uniqueID) as? FLTSavePhotoDelegate // Completion runs on IO queue. let ioQueue = DispatchQueue(label: "io_queue") @@ -138,12 +126,8 @@ final class PhotoCaptureTests: XCTestCase { let mockOutput = MockCapturePhotoOutput() mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in - guard let unwrappedSettings = try? XCTUnwrap(settings, "Settings is unexpectedly nil") else { - XCTFail("Settings is unexpectedly nil") - return - } let delegate = - cam.inProgressSavePhotoDelegates.object(forKey: unwrappedSettings.uniqueID) + cam.inProgressSavePhotoDelegates.object(forKey: settings.uniqueID) as? FLTSavePhotoDelegate // Completion runs on IO queue. let ioQueue = DispatchQueue(label: "io_queue") @@ -190,12 +174,8 @@ final class PhotoCaptureTests: XCTestCase { let filePath = "test" let mockOutput = MockCapturePhotoOutput() mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in - guard let unwrappedSettings = try? XCTUnwrap(settings, "Settings is unexpectedly nil") else { - XCTFail("Settings is unexpectedly nil") - return - } let delegate = - cam.inProgressSavePhotoDelegates.object(forKey: unwrappedSettings.uniqueID) + cam.inProgressSavePhotoDelegates.object(forKey: settings.uniqueID) as? FLTSavePhotoDelegate // Completion runs on IO queue. let ioQueue = DispatchQueue(label: "io_queue") diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCapturePhotoOutput.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCapturePhotoOutput.h index a4e28b60139..f569f739c52 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCapturePhotoOutput.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCapturePhotoOutput.h @@ -20,7 +20,6 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, assign) BOOL highResolutionCaptureEnabled; @property(nonatomic, readonly) NSArray *supportedFlashModes; -// TODO(RobertOdrowaz): Verify if settings and delegate need to be nullable when migrating to Swift. - (void)capturePhotoWithSettings:(AVCapturePhotoSettings *)settings delegate:(NSObject *)delegate; - (nullable AVCaptureConnection *)connectionWithMediaType:(AVMediaType)mediaType;