Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
[image_picker_ios] Pass through error message from image saving
Browse files Browse the repository at this point in the history
  • Loading branch information
jmagman committed Dec 17, 2022
1 parent 32dcbf3 commit 4ec89b8
Show file tree
Hide file tree
Showing 12 changed files with 276 additions and 101 deletions.
4 changes: 4 additions & 0 deletions packages/image_picker/image_picker_ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.8.6+3

* Returns error on image load failure.

## 0.8.6+2

* Fixes issue where selectable images of certain types (such as ProRAW images) could not be loaded.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

@import image_picker_ios;
@import image_picker_ios.Test;
@import UniformTypeIdentifiers;
@import XCTest;

#import <OCMock/OCMock.h>

@interface MockViewController : UIViewController
Expand Down Expand Up @@ -269,37 +271,130 @@ - (void)testViewController {
- (void)testPluginMultiImagePathHasNullItem {
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];

dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
__block FlutterError *pickImageResult = nil;
XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *_Nullable result, FlutterError *_Nullable error) {
pickImageResult = error;
dispatch_semaphore_signal(resultSemaphore);
XCTAssertEqualObjects(error.code, @"create_error");
[resultExpectation fulfill];
}];
[plugin sendCallResultWithSavedPathList:@[ [NSNull null] ]];

dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER);

XCTAssertEqualObjects(pickImageResult.code, @"create_error");
[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (void)testPluginMultiImagePathHasItem {
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
NSArray *pathList = @[ @"test" ];

dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
__block id pickImageResult = nil;
XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];

plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *_Nullable result, FlutterError *_Nullable error) {
pickImageResult = result;
dispatch_semaphore_signal(resultSemaphore);
XCTAssertEqualObjects(result, pathList);
[resultExpectation fulfill];
}];
[plugin sendCallResultWithSavedPathList:pathList];

dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER);
[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (void)testSendsImageInvalidSourceError API_AVAILABLE(ios(14)) {
id mockPickerViewController = OCMClassMock([PHPickerViewController class]);

id mockItemProvider = OCMClassMock([NSItemProvider class]);
// Does not conform to image, invalid source.
OCMStub([mockItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(NO);

PHPickerResult *failResult1 = OCMClassMock([PHPickerResult class]);
OCMStub([failResult1 itemProvider]).andReturn(mockItemProvider);

PHPickerResult *failResult2 = OCMClassMock([PHPickerResult class]);
OCMStub([failResult2 itemProvider]).andReturn(mockItemProvider);

FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];

XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];

plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *result, FlutterError *error) {
XCTAssertTrue(NSThread.isMainThread);
XCTAssertNil(result);
XCTAssertEqualObjects(error.code, @"invalid_source");
[resultExpectation fulfill];
}];

[plugin picker:mockPickerViewController didFinishPicking:@[ failResult1, failResult2 ]];

[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (void)testSendsImageInvalidErrorWhenOneFails API_AVAILABLE(ios(14)) {
id mockPickerViewController = OCMClassMock([PHPickerViewController class]);
NSError *loadDataError = [NSError errorWithDomain:@"PHPickerDomain" code:1234 userInfo:nil];

id mockFailItemProvider = OCMClassMock([NSItemProvider class]);
OCMStub([mockFailItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(YES);
[[mockFailItemProvider stub]
loadDataRepresentationForTypeIdentifier:OCMOCK_ANY
completionHandler:[OCMArg invokeBlockWithArgs:[NSNull null],
loadDataError, nil]];

PHPickerResult *failResult = OCMClassMock([PHPickerResult class]);
OCMStub([failResult itemProvider]).andReturn(mockFailItemProvider);

NSURL *tiffURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"tiffImage"
withExtension:@"tiff"];
NSItemProvider *tiffItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:tiffURL];
PHPickerResult *tiffResult = OCMClassMock([PHPickerResult class]);
OCMStub([tiffResult itemProvider]).andReturn(tiffItemProvider);

FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];

XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];

plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *result, FlutterError *error) {
XCTAssertTrue(NSThread.isMainThread);
XCTAssertNil(result);
XCTAssertEqualObjects(error.code, @"invalid_image");
[resultExpectation fulfill];
}];

[plugin picker:mockPickerViewController didFinishPicking:@[ failResult, tiffResult ]];

[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (void)testSavesImages API_AVAILABLE(ios(14)) {
id mockPickerViewController = OCMClassMock([PHPickerViewController class]);

NSURL *tiffURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"tiffImage"
withExtension:@"tiff"];
NSItemProvider *tiffItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:tiffURL];
PHPickerResult *tiffResult = OCMClassMock([PHPickerResult class]);
OCMStub([tiffResult itemProvider]).andReturn(tiffItemProvider);

NSURL *pngURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"pngImage"
withExtension:@"png"];
NSItemProvider *pngItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:pngURL];
PHPickerResult *pngResult = OCMClassMock([PHPickerResult class]);
OCMStub([pngResult itemProvider]).andReturn(pngItemProvider);

FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];

XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];

plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *result, FlutterError *error) {
XCTAssertTrue(NSThread.isMainThread);
XCTAssertEqual(result.count, 2);
XCTAssertNil(error);
[resultExpectation fulfill];
}];

[plugin picker:mockPickerViewController didFinishPicking:@[ tiffResult, pngResult ]];

XCTAssertEqual(pickImageResult, pathList);
[self waitForExpectationsWithTimeout:30 handler:nil];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveWithTheCorrectExtentionAndM
maxWidth:nil
maxHeight:nil
imageQuality:nil];
XCTAssertNotNil(savedPathJPG);
XCTAssertEqualObjects([savedPathJPG substringFromIndex:savedPathJPG.length - 4], @".jpg");
XCTAssertEqualObjects([NSURL URLWithString:savedPathJPG].pathExtension, @"jpg");

NSDictionary *originalMetaDataJPG = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:dataJPG];
NSData *newDataJPG = [NSData dataWithContentsOfFile:savedPathJPG];
Expand All @@ -55,8 +54,7 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveWithTheCorrectExtentionAndM
maxWidth:nil
maxHeight:nil
imageQuality:nil];
XCTAssertNotNil(savedPathPNG);
XCTAssertEqualObjects([savedPathPNG substringFromIndex:savedPathPNG.length - 4], @".png");
XCTAssertEqualObjects([NSURL URLWithString:savedPathPNG].pathExtension, @"png");

NSDictionary *originalMetaDataPNG = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:dataPNG];
NSData *newDataPNG = [NSData dataWithContentsOfFile:savedPathPNG];
Expand All @@ -69,8 +67,6 @@ - (void)testSaveImageWithPickerInfo_ShouldSaveWithDefaultExtention {
NSString *savedPathJPG = [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil
image:imageJPG
imageQuality:nil];

XCTAssertNotNil(savedPathJPG);
// should be saved as
XCTAssertEqualObjects([savedPathJPG substringFromIndex:savedPathJPG.length - 4],
kFLTImagePickerDefaultSuffix);
Expand Down Expand Up @@ -98,7 +94,7 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveAsGifAnimation {
// test gif
NSData *dataGIF = ImagePickerTestImages.GIFTestData;
UIImage *imageGIF = [UIImage imageWithData:dataGIF];
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)dataGIF, nil);
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)dataGIF, nil);

size_t numberOfFrames = CGImageSourceGetCount(imageSource);

Expand All @@ -107,12 +103,12 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveAsGifAnimation {
maxWidth:nil
maxHeight:nil
imageQuality:nil];
XCTAssertNotNil(savedPathGIF);
XCTAssertEqualObjects([savedPathGIF substringFromIndex:savedPathGIF.length - 4], @".gif");
XCTAssertEqualObjects([NSURL URLWithString:savedPathGIF].pathExtension, @"gif");

NSData *newDataGIF = [NSData dataWithContentsOfFile:savedPathGIF];

CGImageSourceRef newImageSource = CGImageSourceCreateWithData((CFDataRef)newDataGIF, nil);
CGImageSourceRef newImageSource =
CGImageSourceCreateWithData((__bridge CFDataRef)newDataGIF, nil);

size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource);

Expand All @@ -124,7 +120,7 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveAsScalledGifAnimation {
NSData *dataGIF = ImagePickerTestImages.GIFTestData;
UIImage *imageGIF = [UIImage imageWithData:dataGIF];

CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)dataGIF, nil);
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)dataGIF, nil);

size_t numberOfFrames = CGImageSourceGetCount(imageSource);

Expand All @@ -139,7 +135,8 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveAsScalledGifAnimation {
XCTAssertEqual(newImage.size.width, 3);
XCTAssertEqual(newImage.size.height, 2);

CGImageSourceRef newImageSource = CGImageSourceCreateWithData((CFDataRef)newDataGIF, nil);
CGImageSourceRef newImageSource =
CGImageSourceCreateWithData((__bridge CFDataRef)newDataGIF, nil);

size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
// found in the LICENSE file.

#import <OCMock/OCMock.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>

@import image_picker_ios;
@import image_picker_ios.Test;
@import UniformTypeIdentifiers;
@import XCTest;

@interface PickerSaveImageToPathOperationTests : XCTestCase
Expand Down Expand Up @@ -113,17 +113,71 @@ - (void)testSaveTIFFImage API_AVAILABLE(ios(14)) {
[self verifySavingImageWithPickerResult:result fullMetadata:YES];
}

- (void)testNonexistentImage API_AVAILABLE(ios(14)) {
NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"bogus"
withExtension:@"png"];
NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL];
PHPickerResult *result = [self createPickerResultWithProvider:itemProvider];

XCTestExpectation *errorExpectation = [self expectationWithDescription:@"invalid source error"];
FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc]
initWithResult:result
maxHeight:@100
maxWidth:@100
desiredImageQuality:@100
fullMetadata:YES
savedPathBlock:^(NSString *savedPath, FlutterError *error) {
XCTAssertEqualObjects(error.code, @"invalid_source");
[errorExpectation fulfill];
}];

[operation start];
[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (void)testFailingImageLoad API_AVAILABLE(ios(14)) {
NSError *loadDataError = [NSError errorWithDomain:@"PHPickerDomain" code:1234 userInfo:nil];

id mockItemProvider = OCMClassMock([NSItemProvider class]);
OCMStub([mockItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(YES);
[[mockItemProvider stub]
loadDataRepresentationForTypeIdentifier:OCMOCK_ANY
completionHandler:[OCMArg invokeBlockWithArgs:[NSNull null],
loadDataError, nil]];

id pickerResult = OCMClassMock([PHPickerResult class]);
OCMStub([pickerResult itemProvider]).andReturn(mockItemProvider);

XCTestExpectation *errorExpectation = [self expectationWithDescription:@"invalid image error"];

FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc]
initWithResult:pickerResult
maxHeight:@100
maxWidth:@100
desiredImageQuality:@100
fullMetadata:YES
savedPathBlock:^(NSString *savedPath, FlutterError *error) {
XCTAssertEqualObjects(error.code, @"invalid_image");
XCTAssertEqualObjects(error.message, loadDataError.localizedDescription);
XCTAssertEqualObjects(error.details, @"PHPickerDomain");
[errorExpectation fulfill];
}];

[operation start];
[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (void)testSavePNGImageWithoutFullMetadata API_AVAILABLE(ios(14)) {
id photoAssetUtil = OCMClassMock([PHAsset class]);

NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"pngImage"
withExtension:@"png"];
NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL];
PHPickerResult *result = [self createPickerResultWithProvider:itemProvider];
OCMReject([photoAssetUtil fetchAssetsWithLocalIdentifiers:OCMOCK_ANY options:OCMOCK_ANY]);

[self verifySavingImageWithPickerResult:result fullMetadata:NO];
OCMVerify(times(0), [photoAssetUtil fetchAssetsWithLocalIdentifiers:[OCMArg any]
options:[OCMArg any]]);
OCMVerifyAll(photoAssetUtil);
}

/**
Expand Down Expand Up @@ -153,21 +207,26 @@ - (PHPickerResult *)createPickerResultWithProvider:(NSItemProvider *)itemProvide
- (void)verifySavingImageWithPickerResult:(PHPickerResult *)result
fullMetadata:(BOOL)fullMetadata API_AVAILABLE(ios(14)) {
XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"];
XCTestExpectation *operationExpectation =
[self expectationWithDescription:@"Operation completed"];

FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc]
initWithResult:result
maxHeight:@100
maxWidth:@100
desiredImageQuality:@100
fullMetadata:fullMetadata
savedPathBlock:^(NSString *savedPath) {
if ([[NSFileManager defaultManager] fileExistsAtPath:savedPath]) {
[pathExpectation fulfill];
}
savedPathBlock:^(NSString *savedPath, FlutterError *error) {
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:savedPath]);
[pathExpectation fulfill];
}];
operation.completionBlock = ^{
[operationExpectation fulfill];
};

[operation start];
[self waitForExpectations:@[ pathExpectation ] timeout:30];
[self waitForExpectationsWithTimeout:30 handler:nil];
XCTAssertTrue(operation.isFinished);
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ + (GIFInfo *)scaledGIFImage:(NSData *)data
options[(NSString *)kCGImageSourceTypeIdentifierHint] = (NSString *)kUTTypeGIF;

CGImageSourceRef imageSource =
CGImageSourceCreateWithData((CFDataRef)data, (CFDictionaryRef)options);
CGImageSourceCreateWithData((__bridge CFDataRef)data, (CFDictionaryRef)options);

size_t numberOfFrames = CGImageSourceGetCount(imageSource);
NSMutableArray<UIImage *> *images = [NSMutableArray arrayWithCapacity:numberOfFrames];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ + (NSString *)imageTypeSuffixFromType:(FLTImagePickerMIMEType)type {
}

+ (NSDictionary *)getMetaDataFromImageData:(NSData *)imageData {
CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
NSDictionary *metadata =
(NSDictionary *)CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, 0, NULL));
CFRelease(source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
#import <Flutter/Flutter.h>
#import <PhotosUI/PhotosUI.h>

@interface FLTImagePickerPlugin : NSObject <FlutterPlugin>

// For testing only.
- (UIViewController *)viewControllerWithWindow:(UIWindow *)window;
NS_ASSUME_NONNULL_BEGIN

@interface FLTImagePickerPlugin : NSObject <FlutterPlugin>
@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit 4ec89b8

Please sign in to comment.