-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[camera] Run iOS methods on UI thread by default #4140
Changes from 15 commits
dea50fc
da83b51
277d923
9bd75bd
a079af9
bd7d8eb
f0a2bb0
4125244
71e321e
bdfde1a
e6b848c
d59a80d
0f35095
0b63dea
8bc557f
b53ab3e
b6acf82
9f31156
a4cf79e
2b84db2
92c2d00
2722cf4
4a1155a
04566ae
57c63ae
5c69728
e2ba654
17d1f5d
a0be148
ee453bc
cd48ddd
98fe08a
a4e5262
39d1438
3a12cbf
8e8bdfc
3e86910
9093887
1d51203
f0b9f53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| // 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; | ||
| @import XCTest; | ||
| @import AVFoundation; | ||
| #import <OCMock/OCMock.h> | ||
|
|
||
| @interface FLTThreadSafeFlutterResult : NSObject | ||
| @property(readonly, nonatomic) FlutterResult flutterResult; | ||
| @end | ||
|
|
||
| @interface MockFLTThreadSafeFlutterResult : FLTThreadSafeFlutterResult | ||
|
stuartmorgan-g marked this conversation as resolved.
Outdated
|
||
| @property(readonly, nonatomic) NSNotificationCenter *notificationCenter; | ||
| @property(nonatomic, nullable) id receivedResult; | ||
| @end | ||
|
|
||
| @implementation MockFLTThreadSafeFlutterResult | ||
| /** | ||
| Initialize with a notification center. | ||
| */ | ||
| - (id)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter { | ||
| self = [super init]; | ||
| _notificationCenter = notificationCenter; | ||
|
renefloor marked this conversation as resolved.
Outdated
|
||
| return self; | ||
| } | ||
|
|
||
| /** | ||
| Called when result is successful. Sends "successWithData" to the notification center. | ||
| */ | ||
| - (void)successWithData:(id)data { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't there be a thread assertion in this code?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean with the thread assertion? That we verify that it is not the main thread?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't remember what I meant by having that comment in this location unfortunately; maybe I was forgetting the flow and thinking this was after the bounce back to the main thread. However, the more general questions is still valid: the goal of this PR is to ensure that methods are getting callbacks on the main thread, so why isn't there any code in the test that calls into the camera handler with a result object controlled by the test, and actually asserts that the callback was on the main thread? Like this: https://github.com/flutter/plugins/blob/master/packages/local_auth/example/ios/RunnerTests/FLTLocalAuthPluginTests.m#L62-L64 It is not clear to me what in this test would deterministically fail without the fix from this PR.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stuartmorgan I have now added ThreadSafeFlutterResultTests that test that the result is always called on the main thread. These tests in CameraMethodChannelTests just verify that ThreadSafeFlutterResult is used, not how that is working. |
||
| _receivedResult = data; | ||
| [self->_notificationCenter postNotificationName:@"successWithData" object:nil]; | ||
| } | ||
| @end | ||
|
|
||
| @interface CameraPlugin (Test) | ||
| - (void)handleMethodCallWithThreadSafeResult:(FlutterMethodCall *)call | ||
| result:(FLTThreadSafeFlutterResult *)result; | ||
| @end | ||
|
|
||
| @interface CameraMethodChannelTests : XCTestCase | ||
| @property(readonly, nonatomic) CameraPlugin *camera; | ||
| @property(readonly, nonatomic) MockFLTThreadSafeFlutterResult *resultObject; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per the comment in the other file: please remove these and make them local to the test. (Having the object under test be part of the fixture state is always a major red flag to me unless there's a very compelling reason.)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| @property(readonly, nonatomic) NSNotificationCenter *notificationCenter; | ||
| @end | ||
|
|
||
| @implementation CameraMethodChannelTests | ||
|
|
||
| - (void)setUp { | ||
| _camera = [[CameraPlugin alloc] init]; | ||
| _notificationCenter = [[NSNotificationCenter alloc] init]; | ||
|
|
||
| // Setup mocks for initWithCameraName method | ||
| id avCaptureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); | ||
| OCMStub([avCaptureDeviceInputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg anyObjectRef]]) | ||
| .andReturn([AVCaptureInput alloc]); | ||
|
|
||
| id avCaptureSessionMock = OCMClassMock([AVCaptureSession class]); | ||
| OCMStub([avCaptureSessionMock alloc]).andReturn(avCaptureSessionMock); | ||
| OCMStub([avCaptureSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); | ||
|
|
||
| _resultObject = | ||
| [[MockFLTThreadSafeFlutterResult alloc] initWithNotificationCenter:_notificationCenter]; | ||
| } | ||
|
|
||
| - (void)tearDown { | ||
| // Put teardown code here. This method is called after the invocation of each test method in the | ||
| // class. | ||
| } | ||
|
renefloor marked this conversation as resolved.
Outdated
|
||
|
|
||
| - (void)testCreate_ShouldCallResultOnMainThread { | ||
| // Setup method call | ||
|
renefloor marked this conversation as resolved.
Outdated
|
||
| XCTNSNotificationExpectation *notificationExpectation = | ||
| [[XCTNSNotificationExpectation alloc] initWithName:@"successWithData" | ||
| object:nil | ||
| notificationCenter:_notificationCenter]; | ||
|
|
||
| FlutterMethodCall *call = [FlutterMethodCall | ||
| methodCallWithMethodName:@"create" | ||
| arguments:@{@"resolutionPreset" : @"medium", @"enableAudio" : @(1)}]; | ||
|
|
||
| // Call handleMethodCall | ||
|
renefloor marked this conversation as resolved.
Outdated
|
||
| [_camera handleMethodCallWithThreadSafeResult:call result:_resultObject]; | ||
|
|
||
| // Don't expect a result yet | ||
| XCTAssertNil(_resultObject.receivedResult); | ||
|
renefloor marked this conversation as resolved.
Outdated
|
||
|
|
||
| [self waitForExpectations:[NSArray arrayWithObject:notificationExpectation] timeout:0.1]; | ||
|
|
||
| // Expect a result after waiting for thread to switch | ||
| XCTAssertNotNil(_resultObject.receivedResult); | ||
|
|
||
| // Verify the result | ||
| NSDictionary *dictionaryResult = (NSDictionary *)_resultObject.receivedResult; | ||
| XCTAssertNotNil(dictionaryResult); | ||
| XCTAssert([[dictionaryResult allKeys] containsObject:@"cameraId"]); | ||
| } | ||
|
|
||
| @end | ||
Uh oh!
There was an error while loading. Please reload this page.