Skip to content
Merged
206 changes: 206 additions & 0 deletions example/ios/InstabugTests/PrivateViewApiTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#import <PrivateViewApi.h>
#import <Flutter/Flutter.h>
#import "FlutterPluginRegistrar+FlutterEngine.h"


@interface MockFlutterPluginRegistrar : NSObject <FlutterPluginRegistrar>
@end

@implementation MockFlutterPluginRegistrar

@end


@interface PrivateViewApiTests : XCTestCase
@property (nonatomic, strong) PrivateViewApi *api;
@property (nonatomic, strong) id mockFlutterApi;
@property (nonatomic, strong) id mockRegistrar;
@property (nonatomic, strong) id mockFlutterViewController;
@property (nonatomic, strong) id mockEngine;

@end

@implementation PrivateViewApiTests

#pragma mark - Setup / Teardown

- (void)setUp {
[super setUp];


self.mockFlutterApi = OCMClassMock([InstabugPrivateViewApi class]);


MockFlutterPluginRegistrar *mockRegistrar = [[MockFlutterPluginRegistrar alloc] init];

self.mockRegistrar = OCMPartialMock(mockRegistrar);

self.mockEngine = OCMClassMock([FlutterEngine class]);
OCMStub([self.mockRegistrar flutterEngine]).andReturn(self.mockEngine);

self.mockFlutterViewController = OCMClassMock([UIViewController class]);

OCMStub([self.mockEngine viewController]).andReturn(_mockFlutterViewController);

self.api = OCMPartialMock([[PrivateViewApi alloc] initWithFlutterApi:self.mockFlutterApi registrar: self.mockRegistrar]);
}

- (void)tearDown {
[self.mockFlutterApi stopMocking];
[self.mockRegistrar stopMocking];
[self.mockFlutterViewController stopMocking];
[self.mockEngine stopMocking];

self.api = nil;

[super tearDown];
}

#pragma mark - Tests

- (void)testMask_Success {
XCTestExpectation *expectation = [self expectationWithDescription:@"Mask method success"];

CGSize imageSize = CGSizeMake(100, 100); // 100x100 pixels

// Step 2: Create the image using UIGraphicsImageRenderer
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:imageSize];

UIImage *screenshot = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
// Draw a red rectangle as an example
[[UIColor redColor] setFill];
CGRect rect = CGRectMake(0, 0, imageSize.width, imageSize.height);
UIRectFill(rect);
}];

NSArray<NSNumber *> *rectangles = @[@10, @20, @30, @40];
UIView *mockView = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 30, 40)];

OCMStub([self.mockFlutterApi getPrivateViewsWithCompletion:([OCMArg invokeBlockWithArgs:rectangles, [NSNull null], nil])]);



OCMStub([self.mockFlutterViewController view]).andReturn(mockView);


[self.api mask:screenshot completion:^(UIImage *result) {
XCTAssertNotNil(result, @"Masked image should be returned.");
[expectation fulfill];
}];

[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (void)testMask_Error {
XCTestExpectation *expectation = [self expectationWithDescription:@"Mask method with error"];

UIImage *screenshot = [UIImage new];
FlutterError *error = [FlutterError errorWithCode:@"ERROR" message:@"Test error" details:nil];

OCMStub([self.mockFlutterApi getPrivateViewsWithCompletion:([OCMArg invokeBlockWithArgs:[NSNull null], error, nil])]);

[self.api mask:screenshot completion:^(UIImage *result) {
XCTAssertEqual(result, screenshot, @"Original screenshot should be returned on error.");
[expectation fulfill];
}];

[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (void)testGetFlutterViewOrigin_ValidView {
UIView *mockView = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 100, 100)];

OCMStub([self.mockFlutterViewController view]).andReturn(mockView);

UIWindow* testWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[testWindow addSubview:mockView];

CGPoint origin = [self.api getFlutterViewOrigin];

XCTAssertEqual(origin.x, 10);
XCTAssertEqual(origin.y, 20);
}

- (void)testGetFlutterViewOrigin_NilView {

OCMStub([self.mockFlutterViewController view]).andReturn(nil);
//
CGPoint origin = [self.api getFlutterViewOrigin];

XCTAssertEqual(origin.x, 0);
XCTAssertEqual(origin.y, 0);
}

- (void)testDrawMaskedImage {
CGSize size = CGSizeMake(100, 100);
UIGraphicsBeginImageContext(size);
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

NSArray<NSValue *> *privateViews = @[
[NSValue valueWithCGRect:CGRectMake(10, 10, 20, 20)],
[NSValue valueWithCGRect:CGRectMake(30, 30, 10, 10)]
];

UIImage *result = [self.api drawMaskedImage:screenshot withPrivateViews:privateViews];

XCTAssertNotNil(result);
XCTAssertEqual(result.size.width, 100);
XCTAssertEqual(result.size.height, 100);
}

- (void)testConvertToRectangles_ValidInput {
NSArray<NSNumber *> *rectangles = @[@10, @20, @30, @40];
UIView *mockView = [[UIView alloc] initWithFrame:CGRectMake(5, 5, 100, 100)];
OCMStub([self.mockFlutterViewController view]).andReturn(mockView);


NSArray<NSValue *> *converted = [self.api convertToRectangles:rectangles];

XCTAssertEqual(converted.count, 1);
CGRect rect = [converted[0] CGRectValue];
XCTAssertTrue(CGRectEqualToRect(rect, CGRectMake(10, 20, 21, 21)));
}

- (void)testConcurrentMaskCalls {
XCTestExpectation *expectation = [self expectationWithDescription:@"Handle concurrent calls"];

CGSize imageSize = CGSizeMake(100, 100); // 100x100 pixels

// Step 2: Create the image using UIGraphicsImageRenderer
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:imageSize];

UIImage *screenshot = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
// Draw a red rectangle as an example
[[UIColor redColor] setFill];
CGRect rect = CGRectMake(0, 0, imageSize.width, imageSize.height);
UIRectFill(rect);
}];

NSArray<NSNumber *> *rectangles = @[@10, @20, @30, @40];


OCMStub([self.mockFlutterApi getPrivateViewsWithCompletion:([OCMArg invokeBlockWithArgs:rectangles, [NSNull null], nil])]);


dispatch_group_t group = dispatch_group_create();

for (int i = 0; i < 5; i++) {
dispatch_group_enter(group);

[self.api mask:screenshot completion:^(UIImage *result) {
XCTAssertNotNil(result, @"Each call should return a valid image.");
dispatch_group_leave(group);
}];
}

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[expectation fulfill];
});

[self waitForExpectationsWithTimeout:2 handler:nil];
}

@end
2 changes: 2 additions & 0 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe

flutter_ios_podfile_setup
target 'Runner' do
pod 'Instabug', :podspec => 'https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/13.4.2/Instabug.podspec'

use_frameworks!
use_modular_headers!

Expand Down
8 changes: 5 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,28 @@ PODS:

DEPENDENCIES:
- Flutter (from `Flutter`)
- Instabug (from `https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/13.4.2/Instabug.podspec`)
- instabug_flutter (from `.symlinks/plugins/instabug_flutter/ios`)
- OCMock (= 3.6)

SPEC REPOS:
trunk:
- Instabug
- OCMock

EXTERNAL SOURCES:
Flutter:
:path: Flutter
Instabug:
:podspec: https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/13.4.2/Instabug.podspec
instabug_flutter:
:path: ".symlinks/plugins/instabug_flutter/ios"

SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
Instabug: 7a71890217b97b1e32dbca96661845396b66da2f
Instabug: 7aacd5099c11ce96bc49dda40eba0963c06acccc
instabug_flutter: a2df87e3d4d9e410785e0b1ffef4bc64d1f4b787
OCMock: 5ea90566be239f179ba766fd9fbae5885040b992

PODFILE CHECKSUM: 8f7552fd115ace1988c3db54a69e4a123c448f84
PODFILE CHECKSUM: f2e19aef9f983becf80950af8e2d9c1b8f57e7a2

COCOAPODS: 1.14.3
16 changes: 14 additions & 2 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
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 */; };
9D381ECFBB01BD0E978EBDF2 /* Pods_InstabugTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71679BEC094CFF3474195C2E /* Pods_InstabugTests.framework */; };
BEF638212CC82C7C004D29E9 /* PrivateViewApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BEF638202CC82C7C004D29E9 /* PrivateViewApiTests.m */; };
BEF638292CCA5E2B004D29E9 /* Pods_InstabugTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71679BEC094CFF3474195C2E /* Pods_InstabugTests.framework */; };
CC080E112937B7DB0041170A /* InstabugApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC080E102937B7DB0041170A /* InstabugApiTests.m */; };
CC198C61293E1A21007077C8 /* SurveysApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC198C60293E1A21007077C8 /* SurveysApiTests.m */; };
CC359DB92937720C0067A924 /* ApmApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC359DB82937720C0067A924 /* ApmApiTests.m */; };
Expand All @@ -26,6 +27,7 @@
CC9925D7293DFB03001FD3EE /* InstabugLogApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9925D6293DFB03001FD3EE /* InstabugLogApiTests.m */; };
CC9925D9293DFD7F001FD3EE /* RepliesApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9925D8293DFD7F001FD3EE /* RepliesApiTests.m */; };
CCADBDD8293CFED300AE5EB8 /* BugReportingApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCADBDD7293CFED300AE5EB8 /* BugReportingApiTests.m */; };
EDD1293B2F5742BC05EDD9F6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BEF6382E2CCA6D7D004D29E9 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -78,6 +80,9 @@
B03C8370EEFE061BDDDA1DA1 /* Pods-InstabugUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugUITests.debug.xcconfig"; path = "Target Support Files/Pods-InstabugUITests/Pods-InstabugUITests.debug.xcconfig"; sourceTree = "<group>"; };
BA5633844585BB93FE7BCCE7 /* Pods-InstabugTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugTests.profile.xcconfig"; path = "Target Support Files/Pods-InstabugTests/Pods-InstabugTests.profile.xcconfig"; sourceTree = "<group>"; };
BE26C80C2BD55575009FECCF /* IBGCrashReporting+CP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IBGCrashReporting+CP.h"; sourceTree = "<group>"; };
BEF638202CC82C7C004D29E9 /* PrivateViewApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PrivateViewApiTests.m; sourceTree = "<group>"; };
BEF6382C2CCA6176004D29E9 /* instabug_flutter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = instabug_flutter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BEF6382E2CCA6D7D004D29E9 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BF9025BBD0A6FD7B193E903A /* Pods-InstabugTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugTests.debug.xcconfig"; path = "Target Support Files/Pods-InstabugTests/Pods-InstabugTests.debug.xcconfig"; sourceTree = "<group>"; };
C090017925D9A030006F3DAE /* InstabugTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InstabugTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
C090017D25D9A031006F3DAE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand All @@ -102,14 +107,15 @@
buildActionMask = 2147483647;
files = (
65C88E6E8EAE049E32FF2F52 /* Pods_Runner.framework in Frameworks */,
EDD1293B2F5742BC05EDD9F6 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C090017625D9A030006F3DAE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9D381ECFBB01BD0E978EBDF2 /* Pods_InstabugTests.framework in Frameworks */,
BEF638292CCA5E2B004D29E9 /* Pods_InstabugTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -135,6 +141,8 @@
54C1C903B090526284242B67 /* Frameworks */ = {
isa = PBXGroup;
children = (
BEF6382E2CCA6D7D004D29E9 /* Pods_Runner.framework */,
BEF6382C2CCA6176004D29E9 /* instabug_flutter.framework */,
853739F5879F6E4272829F47 /* Pods_Runner.framework */,
71679BEC094CFF3474195C2E /* Pods_InstabugTests.framework */,
F5446C0D3B2623D9BCC7CCE3 /* Pods_InstabugUITests.framework */,
Expand Down Expand Up @@ -194,6 +202,7 @@
C090017A25D9A031006F3DAE /* InstabugTests */ = {
isa = PBXGroup;
children = (
BEF638202CC82C7C004D29E9 /* PrivateViewApiTests.m */,
CC198C60293E1A21007077C8 /* SurveysApiTests.m */,
CC78720A2938D1C5008CB2A5 /* Util */,
CC080E102937B7DB0041170A /* InstabugApiTests.m */,
Expand Down Expand Up @@ -457,6 +466,7 @@
CC080E112937B7DB0041170A /* InstabugApiTests.m in Sources */,
CC198C61293E1A21007077C8 /* SurveysApiTests.m in Sources */,
CCADBDD8293CFED300AE5EB8 /* BugReportingApiTests.m in Sources */,
BEF638212CC82C7C004D29E9 /* PrivateViewApiTests.m in Sources */,
CC9925D9293DFD7F001FD3EE /* RepliesApiTests.m in Sources */,
206286ED2ABD0A1F00925509 /* SessionReplayApiTests.m in Sources */,
CC9925D2293DEB0B001FD3EE /* CrashReportingApiTests.m in Sources */,
Expand Down Expand Up @@ -802,6 +812,7 @@
"@loader_path/Frameworks",
);
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.instabug.InstabugTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "InstabugTests/InstabugTests-Bridging-Header.h";
Expand Down Expand Up @@ -835,6 +846,7 @@
"@loader_path/Frameworks",
);
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.instabug.InstabugTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "InstabugTests/InstabugTests-Bridging-Header.h";
Expand Down
6 changes: 5 additions & 1 deletion ios/Classes/InstabugFlutterPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#import "RepliesApi.h"
#import "SessionReplayApi.h"
#import "SurveysApi.h"
#import "PrivateViewApi.h"

@implementation InstabugFlutterPlugin

Expand All @@ -17,11 +18,14 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
InitBugReportingApi([registrar messenger]);
InitCrashReportingApi([registrar messenger]);
InitFeatureRequestsApi([registrar messenger]);
InitInstabugApi([registrar messenger]);
PrivateViewApi* privateViewApi = InitPrivateViewApi([registrar messenger],registrar);
InitInstabugApi([registrar messenger],privateViewApi);
InitInstabugLogApi([registrar messenger]);
InitRepliesApi([registrar messenger]);
InitSessionReplayApi([registrar messenger]);
InitSurveysApi([registrar messenger]);


}

@end
4 changes: 3 additions & 1 deletion ios/Classes/Modules/InstabugApi.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#import "InstabugPigeon.h"
#import "PrivateViewApi.h"

extern void InitInstabugApi(id<FlutterBinaryMessenger> messenger);
extern void InitInstabugApi(id<FlutterBinaryMessenger> _Nonnull messenger, PrivateViewApi * _Nonnull api);

@interface InstabugApi : NSObject <InstabugHostApi>
@property (nonatomic, strong) PrivateViewApi* _Nonnull privateViewApi;

- (UIImage *)getImageForAsset:(NSString *)assetName;
- (UIFont *)getFontForAsset:(NSString *)assetName error:(FlutterError *_Nullable *_Nonnull)error;
Expand Down
12 changes: 11 additions & 1 deletion ios/Classes/Modules/InstabugApi.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
#import "IBGNetworkLogger+CP.h"
#import "InstabugApi.h"
#import "ArgsRegistry.h"
#import "../Util/Instabug+CP.h"

#define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16)) / 255.0 green:((float)((rgbValue & 0xFF00) >> 8)) / 255.0 blue:((float)(rgbValue & 0xFF)) / 255.0 alpha:((float)((rgbValue & 0xFF000000) >> 24)) / 255.0];

extern void InitInstabugApi(id<FlutterBinaryMessenger> messenger) {
extern void InitInstabugApi(id<FlutterBinaryMessenger> _Nonnull messenger, PrivateViewApi * _Nonnull privateViewApi) {
InstabugApi *api = [[InstabugApi alloc] init];
api.privateViewApi = privateViewApi;
InstabugHostApiSetup(messenger, api);
}

Expand Down Expand Up @@ -53,6 +55,14 @@ - (void)initToken:(NSString *)token invocationEvents:(NSArray<NSString *> *)invo

[Instabug setSdkDebugLogsLevel:resolvedLogLevel];
[Instabug startWithToken:token invocationEvents:resolvedEvents];

[Instabug setScreenshotMaskingHandler:^(UIImage * _Nonnull screenshot, void (^ _Nonnull completion)(UIImage * _Nullable)) {
[self.privateViewApi mask:screenshot completion:^(UIImage * _Nonnull maskedImage) {
if (maskedImage != nil) {
completion(maskedImage);
}
}];
}];
}

- (void)showWithError:(FlutterError *_Nullable *_Nonnull)error {
Expand Down
Loading