diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 0ffe9d4f4..30a39319a 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -17,11 +17,31 @@ 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 143BC5881B21E18100462512 /* ClippingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC57D1B21E18100462512 /* ClippingTests.m */; }; - 143BC5891B21E18100462512 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 143BC57E1B21E18100462512 /* Info.plist */; }; 143BC5A11B21E45C00462512 /* UIExplorerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerIntegrationTests.m */; }; + 144D21241B2204C5006DB32B /* RCTClippingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTClippingTests.m */; }; 147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; }; + 1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */; }; + 1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */; }; + 1497CFAE1B21F5E400C1F8F2 /* RCTContextExecutorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */; }; + 1497CFB01B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */; }; + 1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */; }; + 1497CFB21B21F5E400C1F8F2 /* RCTSparseArrayTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */; }; + 1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */; }; 14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; }; + 14D6D7111B220EB3001FB087 /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14D6D7101B220EB3001FB087 /* libOCMock.a */; }; + 14D6D71E1B2222EF001FB087 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; }; + 14D6D71F1B2222EF001FB087 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; }; + 14D6D7201B2222EF001FB087 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; }; + 14D6D7211B2222EF001FB087 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; }; + 14D6D7221B2222EF001FB087 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; + 14D6D7231B2222EF001FB087 /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; }; + 14D6D7241B2222EF001FB087 /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; }; + 14D6D7251B2222EF001FB087 /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58005BEE1ABA80530062E044 /* libRCTTest.a */; }; + 14D6D7261B2222EF001FB087 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; }; + 14D6D7271B2222EF001FB087 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; }; + 14D6D7281B2222EF001FB087 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; }; + 14D6D7291B2222EF001FB087 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; }; 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; }; 58005BF21ABA80A60062E044 /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58005BEE1ABA80530062E044 /* libRCTTest.a */; }; 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; }; @@ -29,13 +49,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 004D28A41AAF61C70097A701 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 13B07F861A680F5B00A75B9A; - remoteInfo = UIExplorer; - }; 13417FE71AA91428003F314A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */; @@ -145,7 +158,6 @@ 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 = ""; }; 13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; - 143BC57D1B21E18100462512 /* ClippingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClippingTests.m; sourceTree = ""; }; 143BC57E1B21E18100462512 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 143BC5811B21E18100462512 /* testLayoutExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testLayoutExampleSnapshot_1@2x.png"; sourceTree = ""; }; 143BC5821B21E18100462512 /* testSliderExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testSliderExampleSnapshot_1@2x.png"; sourceTree = ""; }; @@ -156,7 +168,26 @@ 143BC5951B21E3E100462512 /* UIExplorerIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 143BC5981B21E3E100462512 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 143BC5A01B21E45C00462512 /* UIExplorerIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerIntegrationTests.m; sourceTree = ""; }; + 144D21231B2204C5006DB32B /* RCTClippingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTClippingTests.m; sourceTree = ""; }; + 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAllocationTests.m; sourceTree = ""; }; + 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeTests.m; sourceTree = ""; }; + 1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutorTests.m; sourceTree = ""; }; + 1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_NSURLTests.m; sourceTree = ""; }; + 1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_UIFontTests.m; sourceTree = ""; }; + 1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTEventDispatcherTests.m; sourceTree = ""; }; + 1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSparseArrayTests.m; sourceTree = ""; }; + 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerTests.m; sourceTree = ""; }; 14AADEFF1AC3DB95002390C9 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../../React/React.xcodeproj; sourceTree = ""; }; + 14D6D7021B220AE3001FB087 /* NSNotificationCenter+OCMAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+OCMAdditions.h"; sourceTree = ""; }; + 14D6D7031B220AE3001FB087 /* OCMArg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMArg.h; sourceTree = ""; }; + 14D6D7041B220AE3001FB087 /* OCMConstraint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMConstraint.h; sourceTree = ""; }; + 14D6D7051B220AE3001FB087 /* OCMLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMLocation.h; sourceTree = ""; }; + 14D6D7061B220AE3001FB087 /* OCMMacroState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMMacroState.h; sourceTree = ""; }; + 14D6D7071B220AE3001FB087 /* OCMock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMock.h; sourceTree = ""; }; + 14D6D7081B220AE3001FB087 /* OCMockObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMockObject.h; sourceTree = ""; }; + 14D6D7091B220AE3001FB087 /* OCMRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMRecorder.h; sourceTree = ""; }; + 14D6D70A1B220AE3001FB087 /* OCMStubRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMStubRecorder.h; sourceTree = ""; }; + 14D6D7101B220EB3001FB087 /* libOCMock.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libOCMock.a; sourceTree = ""; }; 14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = ../../Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj; sourceTree = ""; }; 14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = ""; }; @@ -168,6 +199,19 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 14D6D71E1B2222EF001FB087 /* libRCTActionSheet.a in Frameworks */, + 14D6D71F1B2222EF001FB087 /* libRCTAdSupport.a in Frameworks */, + 14D6D7201B2222EF001FB087 /* libRCTGeolocation.a in Frameworks */, + 14D6D7211B2222EF001FB087 /* libRCTImage.a in Frameworks */, + 14D6D7221B2222EF001FB087 /* libRCTNetwork.a in Frameworks */, + 14D6D7231B2222EF001FB087 /* libRCTPushNotification.a in Frameworks */, + 14D6D7241B2222EF001FB087 /* libRCTSettings.a in Frameworks */, + 14D6D7251B2222EF001FB087 /* libRCTTest.a in Frameworks */, + 14D6D7261B2222EF001FB087 /* libRCTText.a in Frameworks */, + 14D6D7271B2222EF001FB087 /* libRCTVibration.a in Frameworks */, + 14D6D7281B2222EF001FB087 /* libRCTWebSocket.a in Frameworks */, + 14D6D7291B2222EF001FB087 /* libReact.a in Frameworks */, + 14D6D7111B220EB3001FB087 /* libOCMock.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -283,8 +327,18 @@ 143BC57C1B21E18100462512 /* UIExplorerUnitTests */ = { isa = PBXGroup; children = ( - 143BC57D1B21E18100462512 /* ClippingTests.m */, + 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */, + 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */, + 144D21231B2204C5006DB32B /* RCTClippingTests.m */, + 1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */, + 1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */, + 1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */, + 1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */, + 1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */, + 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */, 143BC57E1B21E18100462512 /* Info.plist */, + 14D6D7101B220EB3001FB087 /* libOCMock.a */, + 14D6D7011B220AE3001FB087 /* OCMock */, 143BC57F1B21E18100462512 /* ReferenceImages */, ); path = UIExplorerUnitTests; @@ -344,6 +398,29 @@ name = Products; sourceTree = ""; }; + 14D6D6EA1B2205C0001FB087 /* OCMock */ = { + isa = PBXGroup; + children = ( + ); + path = OCMock; + sourceTree = ""; + }; + 14D6D7011B220AE3001FB087 /* OCMock */ = { + isa = PBXGroup; + children = ( + 14D6D7021B220AE3001FB087 /* NSNotificationCenter+OCMAdditions.h */, + 14D6D7031B220AE3001FB087 /* OCMArg.h */, + 14D6D7041B220AE3001FB087 /* OCMConstraint.h */, + 14D6D7051B220AE3001FB087 /* OCMLocation.h */, + 14D6D7061B220AE3001FB087 /* OCMMacroState.h */, + 14D6D7071B220AE3001FB087 /* OCMock.h */, + 14D6D7081B220AE3001FB087 /* OCMockObject.h */, + 14D6D7091B220AE3001FB087 /* OCMRecorder.h */, + 14D6D70A1B220AE3001FB087 /* OCMStubRecorder.h */, + ); + path = OCMock; + sourceTree = ""; + }; 14DC67E81AB71876001358AB /* Products */ = { isa = PBXGroup; children = ( @@ -375,6 +452,7 @@ 1316A21D1AA397F400C0188E /* Libraries */, 143BC57C1B21E18100462512 /* UIExplorerUnitTests */, 143BC5961B21E3E100462512 /* UIExplorerIntegrationTests */, + 14D6D6EA1B2205C0001FB087 /* OCMock */, 83CBBA001A601CBA00E9B192 /* Products */, ); indentWidth = 2; @@ -413,7 +491,6 @@ buildRules = ( ); dependencies = ( - 004D28A51AAF61C70097A701 /* PBXTargetDependency */, ); name = UIExplorerUnitTests; productName = UIExplorerTests; @@ -466,7 +543,6 @@ TargetAttributes = { 004D289D1AAF61C70097A701 = { CreatedOnToolsVersion = 6.1.1; - TestTargetID = 13B07F861A680F5B00A75B9A; }; 143BC5941B21E3E100462512 = { CreatedOnToolsVersion = 6.3.2; @@ -636,7 +712,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 143BC5891B21E18100462512 /* Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -663,7 +738,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 143BC5881B21E18100462512 /* ClippingTests.m in Sources */, + 1497CFB01B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m in Sources */, + 144D21241B2204C5006DB32B /* RCTClippingTests.m in Sources */, + 1497CFB21B21F5E400C1F8F2 /* RCTSparseArrayTests.m in Sources */, + 1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */, + 1497CFAE1B21F5E400C1F8F2 /* RCTContextExecutorTests.m in Sources */, + 1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */, + 1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */, + 1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */, + 1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -687,11 +770,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 004D28A51AAF61C70097A701 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 13B07F861A680F5B00A75B9A /* UIExplorer */; - targetProxy = 004D28A41AAF61C70097A701 /* PBXContainerItemProxy */; - }; 143BC59C1B21E3E100462512 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 13B07F861A680F5B00A75B9A /* UIExplorer */; @@ -715,7 +793,6 @@ 004D28A61AAF61C70097A701 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", @@ -723,35 +800,52 @@ GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; HEADER_SEARCH_PATHS = ( "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../React/**", + "$(SRCROOT)/UIExplorerUnitTests/**", ); INFOPLIST_FILE = UIExplorerUnitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/UIExplorerUnitTests", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + XCTest, + "-ObjC", + ); PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UIExplorer.app/UIExplorer"; }; name = Debug; }; 004D28A71AAF61C70097A701 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", ); HEADER_SEARCH_PATHS = ( "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../React/**", + "$(SRCROOT)/UIExplorerUnitTests/**", ); INFOPLIST_FILE = UIExplorerUnitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/UIExplorerUnitTests", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + XCTest, + "-ObjC", + ); PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UIExplorer.app/UIExplorer"; }; name = Release; }; @@ -947,6 +1041,7 @@ 143BC59F1B21E3E100462512 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "UIExplorer" */ = { isa = XCConfigurationList; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m b/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m new file mode 100644 index 000000000..c00eedcfd --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m @@ -0,0 +1,79 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import +#import + +#import + +@interface LayoutSubviewsOrderingTest : XCTestCase + +@end + +@implementation LayoutSubviewsOrderingTest + +/** + * This test exists to insure that didLayoutSubviews is always called immediately after layoutSubviews for a VC:View + * pair. In Catalyst we have multiple levels of ViewController containment, and we rely on this ordering + * to insure that layoutGuides are set on RKViewControllers before Views further down in the heirarchy have + * their layoutSubviews called (and need to use the aforementioned layoutGuides) + */ +- (void)testLayoutSubviewsOrdering +{ + // create some Views and ViewControllers + UIViewController *parentVC = [[UIViewController alloc] init]; + UIView *parentView = [[UIView alloc] init]; + UIViewController *childVC = [[UIViewController alloc] init]; + UIView *childView = [[UIView alloc] init]; + + // The ordering we expect is: + // parentView::layoutSubviews + // parentVC::didLayoutSubviews + // childView::layoutSubviews + // childVC::didLayoutSubviews + + id parentViewMock = [OCMockObject partialMockForObject:parentView]; + id parentVCMock = [OCMockObject partialMockForObject:parentVC]; + id childViewMock = [OCMockObject partialMockForObject:childView]; + id childVCMock = [OCMockObject partialMockForObject:childVC]; + + __block int layoutOrderCount = 0; + [[[parentViewMock stub] andDo:^(NSInvocation *inv) { + if (layoutOrderCount < 4) { + layoutOrderCount++; + XCTAssertEqual(layoutOrderCount, 1, @"Expect parentView::layoutSubviews to be called first"); + } + }] layoutSubviews]; + [[[parentVCMock stub] andDo:^(NSInvocation *inv) { + if (layoutOrderCount < 4) { + layoutOrderCount++; + XCTAssertEqual(layoutOrderCount, 2, @"Expect parentVC::viewDidLayoutSubviews to be called 2nd"); + } + }] viewDidLayoutSubviews]; + [[[childViewMock stub] andDo:^(NSInvocation *inv) { + if (layoutOrderCount < 4) { + layoutOrderCount++; + XCTAssertEqual(layoutOrderCount, 3, @"Expect childView::layoutSubviews to be called 3rd"); + } + }] layoutSubviews]; + [[[childVCMock stub] andDo:^(NSInvocation *inv) { + if (layoutOrderCount < 4) { + layoutOrderCount++; + XCTAssertEqual(layoutOrderCount, 4, @"Expect childVC::viewDidLayoutSubviews to be called last"); + [childVCMock stopMocking]; + } + }] viewDidLayoutSubviews]; + + // setup View heirarchy and force layout + parentVC.view = parentView; + childVC.view = childView; + [parentVC addChildViewController:childVC]; + [childVC didMoveToParentViewController:parentVC]; + [parentView addSubview:childView]; + + [childViewMock setNeedsLayout]; + [parentViewMock layoutIfNeeded]; + + XCTAssertEqual(layoutOrderCount, 4, @"Expect layoutSubviews/viewDidLayoutSubviews to be called"); +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/OCMock/NSNotificationCenter+OCMAdditions.h b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/NSNotificationCenter+OCMAdditions.h new file mode 100644 index 000000000..c20a9c2b2 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/NSNotificationCenter+OCMAdditions.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2009-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCObserverMockObject; + + +@interface NSNotificationCenter(OCMAdditions) + +- (void)addMockObserver:(OCObserverMockObject *)notificationObserver name:(NSString *)notificationName object:(id)notificationSender; + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMArg.h b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMArg.h new file mode 100644 index 000000000..d53437cb7 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMArg.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMArg : NSObject + +// constraining arguments + ++ (id)any; ++ (SEL)anySelector; ++ (void *)anyPointer; ++ (id __autoreleasing *)anyObjectRef; ++ (id)isNil; ++ (id)isNotNil; ++ (id)isEqual:(id)value; ++ (id)isNotEqual:(id)value; ++ (id)isKindOfClass:(Class)cls; ++ (id)checkWithSelector:(SEL)selector onObject:(id)anObject; ++ (id)checkWithBlock:(BOOL (^)(id obj))block; + +// manipulating arguments + ++ (id *)setTo:(id)value; ++ (void *)setToValue:(NSValue *)value; + +// internal use only + ++ (id)resolveSpecialValues:(NSValue *)value; + +@end + +#define OCMOCK_ANY [OCMArg any] + +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) + #define OCMOCK_VALUE(variable) \ + ({ __typeof__(variable) __v = (variable); [NSValue value:&__v withObjCType:@encode(__typeof__(__v))]; }) +#else + #define OCMOCK_VALUE(variable) [NSValue value:&variable withObjCType:@encode(__typeof__(variable))] +#endif diff --git a/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMConstraint.h b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMConstraint.h new file mode 100644 index 000000000..777966ab7 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMConstraint.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2007-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + + +@interface OCMConstraint : NSObject + ++ (instancetype)constraint; +- (BOOL)evaluate:(id)value; + +// if you are looking for any, isNil, etc, they have moved to OCMArg + +// try to use [OCMArg checkWith...] instead of the constraintWith... methods below + ++ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject; ++ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject withValue:(id)aValue; + + +@end + +@interface OCMAnyConstraint : OCMConstraint +@end + +@interface OCMIsNilConstraint : OCMConstraint +@end + +@interface OCMIsNotNilConstraint : OCMConstraint +@end + +@interface OCMIsNotEqualConstraint : OCMConstraint +{ + @public + id testValue; +} + +@end + +@interface OCMInvocationConstraint : OCMConstraint +{ + @public + NSInvocation *invocation; +} + +@end + +@interface OCMBlockConstraint : OCMConstraint +{ + BOOL (^block)(id); +} + +- (instancetype)initWithConstraintBlock:(BOOL (^)(id))block; + +@end + + +#define CONSTRAINT(aSelector) [OCMConstraint constraintWithSelector:aSelector onObject:self] +#define CONSTRAINTV(aSelector, aValue) [OCMConstraint constraintWithSelector:aSelector onObject:self withValue:(aValue)] diff --git a/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMLocation.h b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMLocation.h new file mode 100644 index 000000000..e510db7aa --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMLocation.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMLocation : NSObject +{ + id testCase; + NSString *file; + NSUInteger line; +} + ++ (instancetype)locationWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine; + +- (instancetype)initWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine; + +- (id)testCase; +- (NSString *)file; +- (NSUInteger)line; + +@end + +extern OCMLocation *OCMMakeLocation(id testCase, const char *file, int line); diff --git a/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMMacroState.h b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMMacroState.h new file mode 100644 index 000000000..4b2d63508 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMMacroState.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCMLocation; +@class OCMRecorder; +@class OCMStubRecorder; +@class OCMockObject; + + +@interface OCMMacroState : NSObject +{ + OCMRecorder *recorder; +} + ++ (void)beginStubMacro; ++ (OCMStubRecorder *)endStubMacro; + ++ (void)beginExpectMacro; ++ (OCMStubRecorder *)endExpectMacro; + ++ (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation; ++ (void)endVerifyMacro; + ++ (OCMMacroState *)globalState; + +- (OCMRecorder *)recorder; + +- (void)switchToClassMethod; + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMRecorder.h b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMRecorder.h new file mode 100644 index 000000000..f56d2ca4c --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMRecorder.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCMockObject; +@class OCMInvocationMatcher; + + +@interface OCMRecorder : NSProxy +{ + OCMockObject *mockObject; + OCMInvocationMatcher *invocationMatcher; +} + +- (instancetype)init; +- (instancetype)initWithMockObject:(OCMockObject *)aMockObject; + +- (void)setMockObject:(OCMockObject *)aMockObject; + +- (OCMInvocationMatcher *)invocationMatcher; + +- (id)classMethod; +- (id)ignoringNonObjectArgs; + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMStubRecorder.h b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMStubRecorder.h new file mode 100644 index 000000000..890c9ef3b --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMStubRecorder.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2004-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMRecorder.h" + + +@interface OCMStubRecorder : OCMRecorder + +- (id)andReturn:(id)anObject; +- (id)andReturnValue:(NSValue *)aValue; +- (id)andThrow:(NSException *)anException; +- (id)andPost:(NSNotification *)aNotification; +- (id)andCall:(SEL)selector onObject:(id)anObject; +- (id)andDo:(void (^)(NSInvocation *invocation))block; +- (id)andForwardToRealObject; + +@end + + +@interface OCMStubRecorder (Properties) + +#define andReturn(aValue) _andReturn(({ __typeof__(aValue) _v = (aValue); [NSValue value:&_v withObjCType:@encode(__typeof__(_v))]; })) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andReturn)(NSValue *); + +#define andThrow(anException) _andThrow(anException) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andThrow)(NSException *); + +#define andPost(aNotification) _andPost(aNotification) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andPost)(NSNotification *); + +#define andCall(anObject, aSelector) _andCall(anObject, aSelector) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andCall)(id, SEL); + +#define andDo(aBlock) _andDo(aBlock) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andDo)(void (^)(NSInvocation *)); + +#define andForwardToRealObject() _andForwardToRealObject() +@property (nonatomic, readonly) OCMStubRecorder *(^ _andForwardToRealObject)(void); + +@end + + + diff --git a/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMock.h b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMock.h new file mode 100644 index 000000000..f0083b350 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMock.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2004-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import +#import +#import +#import +#import +#import +#import + + +#define OCMClassMock(cls) [OCMockObject niceMockForClass:cls] + +#define OCMStrictClassMock(cls) [OCMockObject mockForClass:cls] + +#define OCMProtocolMock(protocol) [OCMockObject niceMockForProtocol:protocol] + +#define OCMStrictProtocolMock(protocol) [OCMockObject mockForProtocol:protocol] + +#define OCMPartialMock(obj) [OCMockObject partialMockForObject:obj] + +#define OCMObserverMock() [OCMockObject observerMock] + + +#define OCMStub(invocation) \ +({ \ + _OCMSilenceWarnings( \ + [OCMMacroState beginStubMacro]; \ + invocation; \ + [OCMMacroState endStubMacro]; \ + ); \ +}) + +#define OCMExpect(invocation) \ +({ \ + _OCMSilenceWarnings( \ + [OCMMacroState beginExpectMacro]; \ + invocation; \ + [OCMMacroState endExpectMacro]; \ + ); \ +}) + +#define ClassMethod(invocation) \ + _OCMSilenceWarnings( \ + [[OCMMacroState globalState] switchToClassMethod]; \ + invocation; \ + ); + + +#define OCMVerifyAll(mock) [mock verifyAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)] + +#define OCMVerifyAllWithDelay(mock, delay) [mock verifyWithDelay:delay atLocation:OCMMakeLocation(self, __FILE__, __LINE__)] + +#define OCMVerify(invocation) \ +({ \ + _OCMSilenceWarnings( \ + [OCMMacroState beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \ + invocation; \ + [OCMMacroState endVerifyMacro]; \ + ); \ +}) + +#define _OCMSilenceWarnings(macro) \ +({ \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wunused-value\"") \ + macro \ + _Pragma("clang diagnostic pop") \ +}) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMockObject.h b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMockObject.h new file mode 100644 index 000000000..63f2bae2b --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/OCMock/OCMockObject.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2004-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCMLocation; +@class OCMInvocationStub; +@class OCMStubRecorder; +@class OCMInvocationMatcher; +@class OCMInvocationExpectation; + + +@interface OCMockObject : NSProxy +{ + BOOL isNice; + BOOL expectationOrderMatters; + NSMutableArray *stubs; + NSMutableArray *expectations; + NSMutableArray *exceptions; + NSMutableArray *invocations; +} + ++ (id)mockForClass:(Class)aClass; ++ (id)mockForProtocol:(Protocol *)aProtocol; ++ (id)partialMockForObject:(NSObject *)anObject; + ++ (id)niceMockForClass:(Class)aClass; ++ (id)niceMockForProtocol:(Protocol *)aProtocol; + ++ (id)observerMock; + +- (instancetype)init; + +- (void)setExpectationOrderMatters:(BOOL)flag; + +- (id)stub; +- (id)expect; +- (id)reject; + +- (id)verify; +- (id)verifyAtLocation:(OCMLocation *)location; + +- (void)verifyWithDelay:(NSTimeInterval)delay; +- (void)verifyWithDelay:(NSTimeInterval)delay atLocation:(OCMLocation *)location; + +- (void)stopMocking; + +// internal use only + +- (void)addStub:(OCMInvocationStub *)aStub; +- (void)addExpectation:(OCMInvocationExpectation *)anExpectation; + +- (BOOL)handleInvocation:(NSInvocation *)anInvocation; +- (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation; +- (BOOL)handleSelector:(SEL)sel; + +- (void)verifyInvocation:(OCMInvocationMatcher *)matcher; +- (void)verifyInvocation:(OCMInvocationMatcher *)matcher atLocation:(OCMLocation *)location; + +@end + diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m new file mode 100644 index 000000000..5e4ef0be3 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -0,0 +1,191 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import +#import + +#import "RCTBridge.h" +#import "RCTContextExecutor.h" +#import "RCTRootView.h" + +#define RUN_RUNLOOP_WHILE(CONDITION, TIMEOUT) \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Wshadow\"") \ +NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:TIMEOUT]; \ +while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \ + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \ +} \ +_Pragma("clang diagnostic pop") + +#define DEFAULT_TIMEOUT 2 + +@interface RCTBridge (RCTAllocationTests) + +@property (nonatomic, weak) RCTBridge *batchedBridge; + +@end + +@interface RCTJavaScriptContext : NSObject + +@property (nonatomic, assign, readonly) JSGlobalContextRef ctx; + +@end + +@interface AllocationTestModule : NSObject +@end + +@implementation AllocationTestModule + +RCT_EXPORT_MODULE(); + +@synthesize valid = _valid; + +- (id)init +{ + if ((self = [super init])) { + _valid = YES; + } + return self; +} + +- (void)invalidate +{ + _valid = NO; +} + +@end + +@interface RCTAllocationTests : XCTestCase +@end + +@implementation RCTAllocationTests + +- (void)testBridgeIsDeallocated +{ + __weak RCTBridge *weakBridge; + @autoreleasepool { + RCTRootView *view = [[RCTRootView alloc] initWithBundleURL:nil + moduleName:@"" + launchOptions:nil]; + weakBridge = view.bridge; + XCTAssertNotNil(weakBridge, @"RCTBridge should have been created"); + (void)view; + } + + sleep(DEFAULT_TIMEOUT); + XCTAssertNil(weakBridge, @"RCTBridge should have been deallocated"); +} + +- (void)testModulesAreInvalidated +{ + AllocationTestModule *module = [[AllocationTestModule alloc] init]; + @autoreleasepool { + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + moduleProvider:^{ + return @[module]; + } + launchOptions:nil]; + XCTAssertTrue(module.isValid, @"AllocationTestModule should be valid"); + (void)bridge; + } + + /** + * Sleep on the main thread to allow js thread deallocations then run the runloop + * to allow the module to be deallocated on the main thread + */ + sleep(1); + RUN_RUNLOOP_WHILE(module.isValid, 1) + XCTAssertFalse(module.isValid, @"AllocationTestModule should have been invalidated by the bridge"); +} + +- (void)testModulesAreDeallocated +{ + __weak AllocationTestModule *weakModule; + @autoreleasepool { + AllocationTestModule *module = [[AllocationTestModule alloc] init]; + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + moduleProvider:^{ + return @[module]; + } + launchOptions:nil]; + weakModule = module; + XCTAssertNotNil(weakModule, @"AllocationTestModule should have been created"); + (void)bridge; + } + + /** + * Sleep on the main thread to allow js thread deallocations then run the runloop + * to allow the module to be deallocated on the main thread + */ + sleep(1); + RUN_RUNLOOP_WHILE(weakModule, 1) + XCTAssertNil(weakModule, @"AllocationTestModule should have been deallocated"); +} + +- (void)testJavaScriptExecutorIsDeallocated +{ + __weak id weakExecutor; + @autoreleasepool { + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + moduleProvider:nil + launchOptions:nil]; + weakExecutor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"]; + XCTAssertNotNil(weakExecutor, @"JavaScriptExecutor should have been created"); + (void)bridge; + } + + // Sleep on the main thread so the deallocation can happen on the JS thread. + sleep(DEFAULT_TIMEOUT); + XCTAssertNil(weakExecutor, @"JavaScriptExecutor should have been released"); +} + +- (void)testJavaScriptContextIsDeallocated +{ + __weak id weakContext; + @autoreleasepool { + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + moduleProvider:nil + launchOptions:nil]; + id executor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"]; + RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"context"]), DEFAULT_TIMEOUT); + XCTAssertNotNil(weakContext, @"RCTJavaScriptContext should have been created"); + (void)bridge; + } + + sleep(DEFAULT_TIMEOUT); + XCTAssertNil(weakContext, @"RCTJavaScriptContext should have been deallocated"); +} + +- (void)testContentViewIsInvalidated +{ + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:nil launchOptions:nil]; + __weak id rootContentView; + @autoreleasepool { + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@""]; + RUN_RUNLOOP_WHILE(!(rootContentView = [rootView valueForKey:@"contentView"]), DEFAULT_TIMEOUT) + XCTAssertTrue([rootContentView isValid], @"RCTContentView should be valid"); + (void)rootView; + } + + sleep(DEFAULT_TIMEOUT); + XCTAssertFalse([rootContentView isValid], @"RCTContentView should have been invalidated"); +} + +- (void)testUnderlyingBridgeIsDeallocated +{ + RCTBridge *bridge; + __weak id batchedBridge; + @autoreleasepool { + bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:nil launchOptions:nil]; + batchedBridge = bridge.batchedBridge; + XCTAssertTrue([batchedBridge isValid], @"RCTBatchedBridge should be valid"); + [bridge reload]; + } + + // Use RUN_RUNLOOP_WHILE because `batchedBridge` deallocates on the main thread. + RUN_RUNLOOP_WHILE(batchedBridge != nil, DEFAULT_TIMEOUT) + + XCTAssertNotNil(bridge, @"RCTBridge should not have been deallocated"); + XCTAssertNil(batchedBridge, @"RCTBatchedBridge should have been deallocated"); +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m new file mode 100644 index 000000000..ff4b8cae3 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -0,0 +1,183 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import +#import + +#import "RCTBridge.h" +#import "RCTBridgeModule.h" +#import "RCTJavaScriptExecutor.h" +#import "RCTUtils.h" + +@interface RCTBridge (Testing) + +@property (nonatomic, strong, readonly) RCTBridge *batchedBridge; + +- (void)_handleBuffer:(id)buffer context:(NSNumber *)context; +- (void)setUp; + +@end + +@interface TestExecutor : NSObject + +@property (nonatomic, readonly, copy) NSMutableDictionary *injectedStuff; + +@end + +@implementation TestExecutor + +- (instancetype)init +{ + if (self = [super init]) { + _injectedStuff = [NSMutableDictionary dictionary]; + } + return self; +} + +- (BOOL)isValid +{ + return YES; +} + +- (void)executeJSCall:(NSString *)name + method:(NSString *)method + arguments:(NSArray *)arguments + context:(NSNumber *)executorID + callback:(RCTJavaScriptCallback)onComplete +{ + onComplete(nil, nil); +} + +- (void)executeApplicationScript:(NSString *)script + sourceURL:(NSURL *)url + onComplete:(RCTJavaScriptCompleteBlock)onComplete +{ + onComplete(nil); +} + +- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block +{ + block(); +} + +- (void)injectJSONText:(NSString *)script + asGlobalObjectNamed:(NSString *)objectName + callback:(RCTJavaScriptCompleteBlock)onComplete +{ + _injectedStuff[objectName] = script; + onComplete(nil); +} + +- (void)invalidate {} + +@end + +@interface RCTBridgeTests : XCTestCase +{ + RCTBridge *_bridge; + BOOL _testMethodCalled; + dispatch_queue_t _queue; +} +@end + +@implementation RCTBridgeTests + +RCT_EXPORT_MODULE(TestModule) + +- (dispatch_queue_t)methodQueue +{ + return _queue; +} + +- (void)setUp +{ + [super setUp]; + + _queue = dispatch_queue_create("com.facebook.React.TestQueue", DISPATCH_QUEUE_SERIAL); + + _bridge = [[RCTBridge alloc] initWithBundleURL:nil + moduleProvider:^{ return @[self]; } + launchOptions:nil]; + + _bridge.executorClass = [TestExecutor class]; + // Force to recreate the executor with the new class + // - reload: doesn't work here since bridge hasn't loaded yet. + [_bridge invalidate]; + [_bridge setUp]; +} + +- (void)tearDown +{ + [super tearDown]; + [_bridge invalidate]; +} + +- (void)testHookRegistration +{ + TestExecutor *executor = [_bridge.batchedBridge valueForKey:@"_javaScriptExecutor"]; + NSString *injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"]; + NSDictionary *moduleConfig = RCTJSONParse(injectedStuff, NULL); + NSDictionary *remoteModuleConfig = moduleConfig[@"remoteModuleConfig"]; + NSDictionary *testModuleConfig = remoteModuleConfig[@"TestModule"]; + NSDictionary *constants = testModuleConfig[@"constants"]; + NSDictionary *methods = testModuleConfig[@"methods"]; + + XCTAssertNotNil(moduleConfig); + XCTAssertNotNil(remoteModuleConfig); + XCTAssertNotNil(testModuleConfig); + XCTAssertNotNil(constants); + XCTAssertEqualObjects(constants[@"eleventyMillion"], @42); + XCTAssertNotNil(methods); + XCTAssertNotNil(methods[@"testMethod"]); +} + +- (void)testCallNativeMethod +{ + TestExecutor *executor = [_bridge.batchedBridge valueForKey:@"_javaScriptExecutor"]; + NSString *injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"]; + NSDictionary *moduleConfig = RCTJSONParse(injectedStuff, NULL); + NSDictionary *remoteModuleConfig = moduleConfig[@"remoteModuleConfig"]; + NSDictionary *testModuleConfig = remoteModuleConfig[@"TestModule"]; + NSNumber *testModuleID = testModuleConfig[@"moduleID"]; + NSDictionary *methods = testModuleConfig[@"methods"]; + NSDictionary *testMethod = methods[@"testMethod"]; + NSNumber *testMethodID = testMethod[@"methodID"]; + + NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42]; + NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args], @[], @1234567]; + + [_bridge.batchedBridge _handleBuffer:buffer context:RCTGetExecutorID(executor)]; + + dispatch_sync(_queue, ^{ + // clear the queue + XCTAssertTrue(_testMethodCalled); + }); +} + +- (void)DISABLED_testBadArgumentsCount +{ + //NSArray *bufferWithMissingArgument = @[@[@1], @[@0], @[@[@1234, @5678, @"stringy", @{@"a": @1}/*, @42*/]], @[], @1234567]; + //[_bridge _handleBuffer:bufferWithMissingArgument]; + NSLog(@"WARNING: testBadArgumentsCount is temporarily disabled until we have a better way to test cases that we expect to trigger redbox errors"); +} + +RCT_EXPORT_METHOD(testMethod:(NSInteger)integer + number:(NSNumber *)number + string:(NSString *)string + dictionary:(NSDictionary *)dict + callback:(RCTResponseSenderBlock)callback) +{ + _testMethodCalled = YES; + + XCTAssertTrue(integer == 1234); + XCTAssertEqualObjects(number, @5678); + XCTAssertEqualObjects(string, @"stringy"); + XCTAssertEqualObjects(dict, @{@"a": @1}); + XCTAssertNotNil(callback); +} + +- (NSDictionary *)constantsToExport +{ + return @{@"eleventyMillion": @42}; +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/ClippingTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTClippingTests.m similarity index 100% rename from Examples/UIExplorer/UIExplorerUnitTests/ClippingTests.m rename to Examples/UIExplorer/UIExplorerUnitTests/RCTClippingTests.m diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m new file mode 100644 index 000000000..534f9ad91 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m @@ -0,0 +1,184 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +#import + +#import "RCTContextExecutor.h" +#import "RCTUtils.h" + + +@interface RCTContextExecutorTests : XCTestCase + +@end + +@implementation RCTContextExecutorTests + +- (void)testNativeLoggingHookExceptionBehavior +{ + RCTContextExecutor *executor = [[RCTContextExecutor alloc] init]; + dispatch_semaphore_t doneSem = dispatch_semaphore_create(0); + [executor executeApplicationScript:@"var x = {toString: function() { throw 1; }}; nativeLoggingHook(x);" + sourceURL:[NSURL URLWithString:@"file://"] + onComplete:^(id error){ + dispatch_semaphore_signal(doneSem); + }]; + dispatch_semaphore_wait(doneSem, DISPATCH_TIME_FOREVER); + [executor invalidate]; +} + +static uint64_t _get_time_nanoseconds(void) +{ + static struct mach_timebase_info tb_info = {0}; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + int ret = mach_timebase_info(&tb_info); + assert(0 == ret); + }); + + return (mach_absolute_time() * tb_info.numer) / tb_info.denom; +} + +- (void)testDeserializationPerf +{ + // This test case checks the assumption that deserializing objects from JavaScript + // values one-by-one via ObjC JSC API is slower than using JSON string + // You might want to switch your tests schema to "Release" build configuration + + JSContextGroupRef group = JSContextGroupCreate(); + JSGlobalContextRef context = JSGlobalContextCreateInGroup(group, NULL); + id message = @[@[@1, @2, @3, @4], @[@{@"a": @1}, @{@"b": @2}], [NSNull null]]; + NSString *code = RCTJSONStringify(message, NULL); + JSStringRef script = JSStringCreateWithCFString((__bridge CFStringRef)code); + JSValueRef error = NULL; + JSValueRef value = JSEvaluateScript(context, script, NULL, NULL, 0, &error); + XCTAssertTrue(error == NULL); + + id obj; + uint64_t start = _get_time_nanoseconds(); + for (int i = 0; i < 10000; i++) { + JSStringRef jsonJSString = JSValueCreateJSONString(context, value, 0, nil); + NSString *jsonString = (__bridge NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsonJSString); + JSStringRelease(jsonJSString); + + obj = RCTJSONParse(jsonString, NULL); + } + NSLog(@"JSON Parse time: %.2fms", (_get_time_nanoseconds() - start) / 1000000.0); + + JSStringRelease(script); + JSGlobalContextRelease(context); + JSContextGroupRelease(group); +} + +- (void)MANUALLY_testJavaScriptCallSpeed +{ +/** + * Since we almost don't change the RCTContextExecutor logic, and this test is + * very likely to become flaky (specially accross different devices) leave it + * to be run manually + * + * Previous Values: If you change the executor code, you should update this values + */ + + int const runs = 4e5; + int const frequency = 10; + double const threshold = 0.1; + static double const expectedTimes[] = { + 0x1.6199943826cf1p+13, + 0x1.a3bc0a81551c3p+13, + 0x1.d49fbb8602fe3p+13, + 0x1.d1f64085ecb7bp+13, + }; + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + RCTContextExecutor *executor = RCTCreateExecutor([RCTContextExecutor class]); + NSString *script = @" \ + var modules = { \ + module: { \ + method: function () { \ + return true; \ + } \ + } \ + }; \ + function require(module) { \ + return modules[module]; \ + } \ + "; + + [executor executeApplicationScript:script sourceURL:[NSURL URLWithString:@"http://localhost:8081/"] onComplete:^(NSError *error) { + NSMutableArray *params = [[NSMutableArray alloc] init]; + id data = @1; + for (int i = 0; i < 4; i++) { + double samples[runs / frequency]; + int size = runs / frequency; + double total = 0; + for (int j = 0; j < runs; j++) { + @autoreleasepool { + double start = _get_time_nanoseconds(); + [executor executeJSCall:@"module" + method:@"method" + arguments:params + context:RCTGetExecutorID(executor) + callback:^(id json, NSError *__error) { + RCTAssert([json isEqual:@YES], @"Invalid return"); + }]; + double run = _get_time_nanoseconds() - start; + if ((j % frequency) == frequency - 1) { // Warmup + total += run; + samples[j/frequency] = run; + } + } + } + + double mean = total / size; + double variance = 0; + + for (int j = 0; j < size; j++) { + variance += pow(samples[j] - mean, 2); + } + variance /= size; + + double standardDeviation = sqrt(variance); + double marginOfError = standardDeviation * 1.645; + + double lower = mean - marginOfError; + double upper = mean + marginOfError; + + int s = 0; + total = 0; + for (int j = 0; j < size; j++) { + double v = samples[j]; + if (v >= lower && v <= upper) { + samples[s++] = v; + total += v; + } + } + mean = total / s; + + lower = mean * (1.0 - threshold); + upper = mean * (1.0 + threshold); + + double expected = expectedTimes[i]; + + NSLog(@"Previous: %lf, New: %f -> %a", expected, mean, mean); + if (upper < expected) { + NSLog(@"You made JS calls with %d argument(s) %.2lf%% faster :) - Remember to update the tests with the new value: %a", + i, (1 - (double)mean / expected) * 100, mean); + } + + + XCTAssertTrue(lower < expected, @"You made JS calls with %d argument(s) %.2lf%% slower :( - If that's *really* necessary, update the tests with the new value: %a", + i, ((double)mean / expected - 1) * 100, mean); + + [params addObject:data]; + } + dispatch_semaphore_signal(semaphore); + }]; + + while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]; + } +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m new file mode 100644 index 000000000..05c07fa06 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m @@ -0,0 +1,48 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +#import "RCTConvert.h" + +@interface RCTConvert_NSURLTests : XCTestCase + +@end + +@implementation RCTConvert_NSURLTests + +#define TEST_URL(name, _input, _expectedURL) \ +- (void)test_##name { \ + NSURL *result = [RCTConvert NSURL:_input]; \ + NSURL *expected = [NSURL URLWithString:_expectedURL]; \ + XCTAssertEqualObjects(result.absoluteURL, expected); \ +} \ + +#define TEST_PATH(name, _input, _expectedPath) \ +- (void)test_##name { \ + NSURL *result = [RCTConvert NSURL:_input]; \ + XCTAssertEqualObjects(result.path, _expectedPath); \ +} \ + +#define TEST_BUNDLE_PATH(name, _input, _expectedPath) \ +TEST_PATH(name, _input, [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:_expectedPath]) + +// Basic tests +TEST_URL(basic, @"http://example.com", @"http://example.com") +TEST_URL(null, [NSNull null], nil) + +// Local files +TEST_PATH(fileURL, @"file:///blah/hello.jsbundle", @"/blah/hello.jsbundle") +TEST_BUNDLE_PATH(filePath, @"blah/hello.jsbundle", @"blah/hello.jsbundle") +TEST_BUNDLE_PATH(filePathWithSpaces, @"blah blah/hello.jsbundle", @"blah blah/hello.jsbundle") +TEST_BUNDLE_PATH(filePathWithEncodedSpaces, @"blah%20blah/hello.jsbundle", @"blah blah/hello.jsbundle") +TEST_BUNDLE_PATH(imageAt2XPath, @"images/foo@2x.jpg", @"images/foo@2x.jpg") +TEST_BUNDLE_PATH(imageFile, @"foo.jpg", @"foo.jpg") + +// Remote files +TEST_URL(fullURL, @"http://example.com/blah/hello.jsbundle", @"http://example.com/blah/hello.jsbundle") +TEST_URL(urlWithSpaces, @"http://example.com/blah blah/foo", @"http://example.com/blah%20blah/foo") +TEST_URL(urlWithEncodedSpaces, @"http://example.com/blah%20blah/foo", @"http://example.com/blah%20blah/foo") +TEST_URL(imageURL, @"http://example.com/foo@2x.jpg", @"http://example.com/foo@2x.jpg") +TEST_URL(imageURLWithSpaces, @"http://example.com/blah foo@2x.jpg", @"http://example.com/blah%20foo@2x.jpg") + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m new file mode 100644 index 000000000..35dfe1167 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m @@ -0,0 +1,159 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +#import "RCTConvert.h" + +@interface RCTConvert_UIFontTests : XCTestCase + +@end + +@implementation RCTConvert_UIFontTests + +#define RCTAssertEqualFonts(font1, font2) { \ + XCTAssertEqualObjects(font1, font2); \ +} + +- (void)DISABLED_testWeight // task #7118691 +{ + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Bold" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"bold"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Medium" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"500"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-UltraLight" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"100"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"normal"}]; + RCTAssertEqualFonts(expected, result); + } +} + +- (void)testSize +{ + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:18.5]; + UIFont *result = [RCTConvert UIFont:@{@"fontSize": @18.5}]; + RCTAssertEqualFonts(expected, result); + } +} + +- (void)testFamily +{ + { + UIFont *expected = [UIFont fontWithName:@"Cochin" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Cochin"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Helvetica Neue"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Italic" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"HelveticaNeue-Italic"}]; + RCTAssertEqualFonts(expected, result); + } +} + +- (void)testStyle +{ + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Italic" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"italic"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"normal"}]; + RCTAssertEqualFonts(expected, result); + } +} + +- (void)DISABLED_testStyleAndWeight // task #7118691 +{ + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-UltraLightItalic" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"italic", @"fontWeight": @"100"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-BoldItalic" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"italic", @"fontWeight": @"bold"}]; + RCTAssertEqualFonts(expected, result); + } +} + +- (void)DISABLED_testFamilyAndWeight // task #7118691 +{ + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Bold" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Helvetica Neue", @"fontWeight": @"bold"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"HelveticaNeue-Bold", @"fontWeight": @"normal"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"Cochin-Bold" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Cochin", @"fontWeight": @"700"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"Cochin" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Cochin", @"fontWeight": @"500"}]; // regular Cochin is actually medium bold + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"Cochin" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Cochin", @"fontWeight": @"100"}]; + RCTAssertEqualFonts(expected, result); + } +} + +- (void)testFamilyAndStyle +{ + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Italic" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Helvetica Neue", @"fontStyle": @"italic"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"HelveticaNeue-Italic", @"fontStyle": @"normal"}]; + RCTAssertEqualFonts(expected, result); + } +} + +- (void)DISABLED_testFamilyStyleAndWeight // task #7118691 +{ + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-UltraLightItalic" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Helvetica Neue", @"fontStyle": @"italic", @"fontWeight": @"100"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Bold" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"HelveticaNeue-Italic", @"fontStyle": @"normal", @"fontWeight": @"bold"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"HelveticaNeue-Italic", @"fontStyle": @"normal", @"fontWeight": @"normal"}]; + RCTAssertEqualFonts(expected, result); + } +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m new file mode 100644 index 000000000..188414ef9 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m @@ -0,0 +1,144 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import +#import "RCTEventDispatcher.h" + +@interface RCTTestEvent : RCTBaseEvent + +@property (nonatomic, assign) BOOL canCoalesce; + +@end + +@implementation RCTTestEvent + +- (instancetype)initWithViewTag:(NSNumber *)viewTag eventName:(NSString *)eventName body:(NSDictionary *)body +{ + if (self = [super initWithViewTag:viewTag eventName:eventName body:body]) { + self.canCoalesce = YES; + } + return self; +} + ++ (NSString *)moduleDotMethod +{ + return @"RCTDeviceEventEmitter.emit"; +} + +@end + +@interface RCTEventDispatcherTests : XCTestCase +@end + +@implementation RCTEventDispatcherTests +{ + id _bridge; + RCTEventDispatcher *_eventDispatcher; + + NSString *_eventName; + NSDictionary *_body; + RCTTestEvent *_testEvent; + NSString *_JSMethod; +} + + +- (void)setUp +{ + [super setUp]; + + _bridge = [OCMockObject mockForClass:[RCTBridge class]]; + _eventDispatcher = [[RCTEventDispatcher alloc] init]; + ((id)_eventDispatcher).bridge = _bridge; + + _eventName = @"sampleEvent"; + _body = @{ @"foo": @"bar" }; + _testEvent = [[RCTTestEvent alloc] initWithViewTag:nil + eventName:_eventName + body:_body]; + + _JSMethod = [[_testEvent class] moduleDotMethod]; +} + +- (void)testLegacyEventsAreImmediatelyDispatched +{ + [[_bridge expect] enqueueJSCall:_JSMethod + args:@[_eventName, _body]]; + + [_eventDispatcher sendDeviceEventWithName:_eventName body:_body]; + + [_bridge verify]; +} + +- (void)testNonCoalescingEventsAreImmediatelyDispatched +{ + _testEvent.canCoalesce = NO; + [[_bridge expect] enqueueJSCall:_JSMethod + args:@[_eventName, _body]]; + + [_eventDispatcher sendEvent:_testEvent]; + + [_bridge verify]; +} + +- (void)testCoalescedEventShouldBeDispatchedOnFrameUpdate +{ + [_eventDispatcher sendEvent:_testEvent]; + + [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit" + args:@[_eventName, _body]]; + + [(id)_eventDispatcher didUpdateFrame:nil]; + + [_bridge verify]; +} + +- (void)testBasicCoalescingReturnsLastEvent +{ + RCTTestEvent *ignoredEvent = [[RCTTestEvent alloc] initWithViewTag:nil + eventName:_eventName + body:@{ @"other": @"body" }]; + + [_eventDispatcher sendEvent:ignoredEvent]; + [_eventDispatcher sendEvent:_testEvent]; + + [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit" + args:@[_eventName, _body]]; + + [(id)_eventDispatcher didUpdateFrame:nil]; + + [_bridge verify]; +} + +- (void)testDifferentEventTypesDontCoalesce +{ + NSString *firstEventName = @"firstEvent"; + RCTTestEvent *firstEvent = [[RCTTestEvent alloc] initWithViewTag:nil + eventName:firstEventName + body:_body]; + + [_eventDispatcher sendEvent:firstEvent]; + [_eventDispatcher sendEvent:_testEvent]; + + [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit" + args:@[firstEventName, _body]]; + + [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit" + args:@[_eventName, _body]]; + + [(id)_eventDispatcher didUpdateFrame:nil]; + + [_bridge verify]; +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m new file mode 100644 index 000000000..90bf824bd --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m @@ -0,0 +1,43 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +#import "RCTSparseArray.h" +#import "UIView+React.h" + +@interface RCTSparseArrayTests : XCTestCase + +@end + +@implementation RCTSparseArrayTests + +- (void)testDictionary +{ + NSObject *myView = [[UIView alloc] init]; + myView.reactTag = @4; + + NSObject *myOtherView = [[UIView alloc] init]; + myOtherView.reactTag = @5; + + RCTSparseArray *registry = [[RCTSparseArray alloc] init]; + XCTAssertNil(registry[@4], @"how did you have a view when none are registered?"); + XCTAssertNil(registry[@5], @"how did you have a view when none are registered?"); + + registry[myView.reactTag] = myView; + XCTAssertEqual(registry[@4], myView); + XCTAssertNil(registry[@5], @"didn't register other view yet"); + + registry[myOtherView.reactTag] = myOtherView; + XCTAssertEqual(registry[@4], myView); + XCTAssertEqual(registry[@5], myOtherView); + + registry[myView.reactTag] = nil; + XCTAssertNil(registry[@4]); + XCTAssertEqual(registry[@5], myOtherView); + + registry[myOtherView.reactTag] = nil; + XCTAssertNil(registry[@4], @"how did you have a view when none are registered?"); + XCTAssertNil(registry[@5], @"how did you have a view when none are registered?"); +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m new file mode 100644 index 000000000..3a4f62027 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m @@ -0,0 +1,179 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +#import "RCTSparseArray.h" +#import "RCTUIManager.h" +#import "UIView+React.h" + +@interface RCTUIManager (Testing) + +- (void)_manageChildren:(NSNumber *)containerReactTag + moveFromIndices:(NSArray *)moveFromIndices + moveToIndices:(NSArray *)moveToIndices + addChildReactTags:(NSArray *)addChildReactTags + addAtIndices:(NSArray *)addAtIndices + removeAtIndices:(NSArray *)removeAtIndices + registry:(RCTSparseArray *)registry; + +@property (nonatomic, readonly) RCTSparseArray *viewRegistry; + +@end + +@interface RCTUIManagerTests : XCTestCase + +@property (nonatomic, readwrite, strong) RCTUIManager *uiManager; + +@end + +@implementation RCTUIManagerTests + +- (void)setUp +{ + [super setUp]; + + _uiManager = [[RCTUIManager alloc] init]; + + // Register 20 views to use in the tests + for (NSInteger i = 1; i <= 20; i++) { + UIView *registeredView = [[UIView alloc] init]; + [registeredView setReactTag:@(i)]; + _uiManager.viewRegistry[i] = registeredView; + } +} + +- (void)testManagingChildrenToAddViews +{ + UIView *containerView = _uiManager.viewRegistry[20]; + NSMutableArray *addedViews = [NSMutableArray array]; + + NSArray *tagsToAdd = @[@1, @2, @3, @4, @5]; + NSArray *addAtIndices = @[@0, @1, @2, @3, @4]; + for (NSNumber *tag in tagsToAdd) { + [addedViews addObject:_uiManager.viewRegistry[tag]]; + } + + // Add views 1-5 to view 20 + [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil + addChildReactTags:tagsToAdd + addAtIndices:addAtIndices + removeAtIndices:nil + registry:_uiManager.viewRegistry]; + + XCTAssertTrue([[containerView reactSubviews] count] == 5, + @"Expect to have 5 react subviews after calling manage children \ + with 5 tags to add, instead have %lu", (unsigned long)[[containerView reactSubviews] count]); + for (UIView *view in addedViews) { + XCTAssertTrue([view superview] == containerView, + @"Expected to have manage children successfully add children"); + [view removeFromSuperview]; + } +} + +- (void)testManagingChildrenToRemoveViews +{ + UIView *containerView = _uiManager.viewRegistry[20]; + NSMutableArray *removedViews = [NSMutableArray array]; + + NSArray *removeAtIndices = @[@0, @4, @8, @12, @16]; + for (NSNumber *index in removeAtIndices) { + NSNumber *reactTag = @([index integerValue] + 2); + [removedViews addObject:_uiManager.viewRegistry[reactTag]]; + } + for (NSInteger i = 2; i < 20; i++) { + UIView *view = _uiManager.viewRegistry[i]; + [containerView addSubview:view]; + } + + // Remove views 1-5 from view 20 + [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil + addChildReactTags:nil + addAtIndices:nil + removeAtIndices:removeAtIndices + registry:_uiManager.viewRegistry]; + + XCTAssertEqual(containerView.reactSubviews.count, (NSUInteger)13, + @"Expect to have 13 react subviews after calling manage children\ + with 5 tags to remove and 18 prior children, instead have %zd", + containerView.reactSubviews.count); + for (UIView *view in removedViews) { + XCTAssertTrue([view superview] == nil, + @"Expected to have manage children successfully remove children"); + // After removing views are unregistered - we need to reregister + _uiManager.viewRegistry[view.reactTag] = view; + } + for (NSInteger i = 2; i < 20; i++) { + UIView *view = _uiManager.viewRegistry[i]; + if (![removedViews containsObject:view]) { + XCTAssertTrue([view superview] == containerView, + @"Should not have removed view with react tag %ld during delete but did", (long)i); + [view removeFromSuperview]; + } + } +} + +// We want to start with views 1-10 added at indices 0-9 +// Then we'll remove indices 2, 3, 5 and 8 +// Add views 11 and 12 to indices 0 and 6 +// And move indices 4 and 9 to 1 and 7 +// So in total it goes from: +// [1,2,3,4,5,6,7,8,9,10] +// to +// [11,5,1,2,7,8,12,10] +- (void)testManagingChildrenToAddRemoveAndMove +{ + UIView *containerView = _uiManager.viewRegistry[20]; + + NSArray *removeAtIndices = @[@2, @3, @5, @8]; + NSArray *addAtIndices = @[@0, @6]; + NSArray *tagsToAdd = @[@11, @12]; + NSArray *moveFromIndices = @[@4, @9]; + NSArray *moveToIndices = @[@1, @7]; + + // We need to keep these in array to keep them around + NSMutableArray *viewsToRemove = [NSMutableArray array]; + for (NSInteger i = 0; i < removeAtIndices.count; i++) { + NSNumber *reactTagToRemove = @([removeAtIndices[i] integerValue] + 1); + UIView *viewToRemove = _uiManager.viewRegistry[reactTagToRemove]; + [viewsToRemove addObject:viewToRemove]; + } + + for (NSInteger i = 1; i < 11; i++) { + UIView *view = _uiManager.viewRegistry[i]; + [containerView addSubview:view]; + } + + [_uiManager _manageChildren:@20 + moveFromIndices:moveFromIndices + moveToIndices:moveToIndices + addChildReactTags:tagsToAdd + addAtIndices:addAtIndices + removeAtIndices:removeAtIndices + registry:_uiManager.viewRegistry]; + + XCTAssertTrue([[containerView reactSubviews] count] == 8, + @"Expect to have 8 react subviews after calling manage children,\ + instead have the following subviews %@", [containerView reactSubviews]); + + NSArray *expectedReactTags = @[@11, @5, @1, @2, @7, @8, @12, @10]; + for (NSInteger i = 0; i < [[containerView subviews] count]; i++) { + XCTAssertEqualObjects([[containerView reactSubviews][i] reactTag], expectedReactTags[i], + @"Expected subview at index %ld to have react tag #%@ but has tag #%@", + (long)i, expectedReactTags[i], [[containerView reactSubviews][i] reactTag]); + } + + // Clean up after ourselves + for (NSInteger i = 1; i < 13; i++) { + UIView *view = _uiManager.viewRegistry[i]; + [view removeFromSuperview]; + } + for (UIView *view in viewsToRemove) { + _uiManager.viewRegistry[view.reactTag] = view; + } +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/libOCMock.a b/Examples/UIExplorer/UIExplorerUnitTests/libOCMock.a new file mode 100644 index 000000000..9bb38e21a Binary files /dev/null and b/Examples/UIExplorer/UIExplorerUnitTests/libOCMock.a differ