diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 95f9215cee9840..2f62d3066d69e0 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 13B6C1A31C34225900D3FAF5 /* RCTURLUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B6C1A21C34225900D3FAF5 /* RCTURLUtilsTests.m */; }; + 13BCE84F1C9C209600DD7AAD /* RCTComponentPropsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BCE84E1C9C209600DD7AAD /* RCTComponentPropsTests.m */; }; 13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */; }; 13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */; }; 143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; }; @@ -204,6 +205,7 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = UIExplorer/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = UIExplorer/main.m; sourceTree = ""; }; 13B6C1A21C34225900D3FAF5 /* RCTURLUtilsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTURLUtilsTests.m; sourceTree = ""; }; + 13BCE84E1C9C209600DD7AAD /* RCTComponentPropsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTComponentPropsTests.m; sourceTree = ""; }; 13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSONTests.m; sourceTree = ""; }; 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMethodArgumentTests.m; sourceTree = ""; }; @@ -431,6 +433,7 @@ 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */, 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */, 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */, + 13BCE84E1C9C209600DD7AAD /* RCTComponentPropsTests.m */, 143BC57E1B21E18100462512 /* Info.plist */, 14D6D7101B220EB3001FB087 /* libOCMock.a */, 14D6D7011B220AE3001FB087 /* OCMock */, @@ -883,6 +886,7 @@ buildActionMask = 2147483647; files = ( 1497CFB01B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m in Sources */, + 13BCE84F1C9C209600DD7AAD /* RCTComponentPropsTests.m in Sources */, 144D21241B2204C5006DB32B /* RCTImageUtilTests.m in Sources */, 1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */, 1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */, diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTComponentPropsTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTComponentPropsTests.m new file mode 100644 index 00000000000000..75a32bdac70ba2 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTComponentPropsTests.m @@ -0,0 +1,181 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#import "RCTUIManager.h" +#import "RCTViewManager.h" +#import "RCTView.h" + +#define RUN_RUNLOOP_WHILE(CONDITION) \ +{ \ + NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \ + while ((CONDITION)) { \ + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \ + if ([timeout timeIntervalSinceNow] <= 0) { \ + XCTFail(@"Runloop timed out before condition was met"); \ + break; \ + } \ + } \ +} + +@interface RCTUIManager () + +- (void)createView:(NSNumber *)reactTag + viewName:(NSString *)viewName + rootTag:(NSNumber *)rootTag + props:(NSDictionary *)props; + +- (void)updateView:(nonnull NSNumber *)reactTag + viewName:(NSString *)viewName + props:(NSDictionary *)props; + +@end + +@interface RCTPropsTestView : UIView + +@property (nonatomic, assign) NSInteger integerProp; +@property (nonatomic, strong) id objectProp; +@property (nonatomic, assign) CGPoint structProp; +@property (nonatomic, copy) NSString *customProp; + +@end + +@implementation RCTPropsTestView +@end + +@interface RCTPropsTestViewManager : RCTViewManager +@end + +@implementation RCTPropsTestViewManager + +RCT_EXPORT_MODULE() + +- (UIView *)view +{ + RCTPropsTestView *view = [RCTPropsTestView new]; + view.integerProp = 57; + view.objectProp = @9; + view.structProp = CGPointMake(5, 6); + view.customProp = @"Hello"; + return view; +} + +RCT_EXPORT_VIEW_PROPERTY(integerProp, NSInteger) +RCT_EXPORT_VIEW_PROPERTY(objectProp, NSNumber) +RCT_EXPORT_VIEW_PROPERTY(structProp, CGPoint) +RCT_CUSTOM_VIEW_PROPERTY(customProp, NSString, RCTPropsTestView) +{ + view.customProp = json ? [RCTConvert NSString:json] : defaultView.customProp; +} + +@end + +@interface RCTComponentPropsTests : XCTestCase + +@end + +@implementation RCTComponentPropsTests +{ + RCTBridge *_bridge; +} + +- (void)setUp +{ + [super setUp]; + + _bridge = [[RCTBridge alloc] initWithBundleURL:nil + moduleProvider:nil + launchOptions:nil]; +} + +- (void)testSetProps +{ + __block RCTPropsTestView *view; + RCTUIManager *uiManager = _bridge.uiManager; + NSDictionary *props = @{@"integerProp": @58, + @"objectProp": @10, + @"structProp": @{@"x": @7, @"y": @8}, + @"customProp": @"Goodbye"}; + + dispatch_async(uiManager.methodQueue, ^{ + [uiManager createView:@2 viewName:@"RCTPropsTestView" rootTag:nil props:props]; + [uiManager addUIBlock:^(__unused RCTUIManager *_uiManager, NSDictionary *viewRegistry) { + view = (RCTPropsTestView *)viewRegistry[@2]; + XCTAssertEqual(view.integerProp, 58); + XCTAssertEqualObjects(view.objectProp, @10); + XCTAssertTrue(CGPointEqualToPoint(view.structProp, CGPointMake(7, 8))); + XCTAssertEqualObjects(view.customProp, @"Goodbye"); + }]; + [uiManager setNeedsLayout]; + }); + + RUN_RUNLOOP_WHILE(view == nil); +} + +- (void)testResetProps +{ + __block RCTPropsTestView *view; + RCTUIManager *uiManager = _bridge.uiManager; + NSDictionary *props = @{@"integerProp": @58, + @"objectProp": @10, + @"structProp": @{@"x": @7, @"y": @8}, + @"customProp": @"Goodbye"}; + + NSDictionary *resetProps = @{@"integerProp": [NSNull null], + @"objectProp": [NSNull null], + @"structProp": [NSNull null], + @"customProp": [NSNull null]}; + + dispatch_async(uiManager.methodQueue, ^{ + [uiManager createView:@2 viewName:@"RCTPropsTestView" rootTag:nil props:props]; + [uiManager updateView:@2 viewName:@"RCTPropsTestView" props:resetProps]; + [uiManager addUIBlock:^(__unused RCTUIManager *_uiManager, NSDictionary *viewRegistry) { + view = (RCTPropsTestView *)viewRegistry[@2]; + XCTAssertEqual(view.integerProp, 57); + XCTAssertEqualObjects(view.objectProp, @9); + XCTAssertTrue(CGPointEqualToPoint(view.structProp, CGPointMake(5, 6))); + XCTAssertEqualObjects(view.customProp, @"Hello"); + }]; + [uiManager setNeedsLayout]; + }); + + RUN_RUNLOOP_WHILE(view == nil); +} + +- (void)testResetBackgroundColor +{ + __block RCTView *view; + RCTUIManager *uiManager = _bridge.uiManager; + NSDictionary *props = @{@"backgroundColor": @0xffffffff}; + NSDictionary *resetProps = @{@"backgroundColor": [NSNull null]}; + + dispatch_async(uiManager.methodQueue, ^{ + [uiManager createView:@2 viewName:@"RCTView" rootTag:nil props:props]; + [uiManager addUIBlock:^(__unused RCTUIManager *_uiManager, NSDictionary *viewRegistry) { + view = (RCTView *)viewRegistry[@2]; + XCTAssertEqualObjects(view.backgroundColor, [RCTConvert UIColor:@0xffffffff]); + }]; + [uiManager updateView:@2 viewName:@"RCTView" props:resetProps]; + [uiManager addUIBlock:^(__unused RCTUIManager *_uiManager, __unused NSDictionary *viewRegistry) { + view = (RCTView *)viewRegistry[@2]; + XCTAssertNil(view.backgroundColor); + }]; + [uiManager setNeedsLayout]; + }); + + RUN_RUNLOOP_WHILE(view == nil); +} + +@end