diff --git a/Examples/UIExplorer/NavigatorIOSColorsExample.js b/Examples/UIExplorer/NavigatorIOSColorsExample.js index a045807c7..0f695d956 100644 --- a/Examples/UIExplorer/NavigatorIOSColorsExample.js +++ b/Examples/UIExplorer/NavigatorIOSColorsExample.js @@ -45,7 +45,7 @@ var NavigatorIOSColors = React.createClass({ render: function() { // Set StatusBar with light contents to get better contrast - StatusBarIOS.setStyle(StatusBarIOS.Style.lightContent); + StatusBarIOS.setStyle('light-content'); return ( ', rightButtonTitle: 'Done', onRightButtonPress: () => { - StatusBarIOS.setStyle(StatusBarIOS.Style['default']); + StatusBarIOS.setStyle('default'); this.props.onExampleExit(); }, passProps: { diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 8dea9bc79..101bae6f9 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -7,20 +7,42 @@ objects = { /* Begin PBXBuildFile section */ - 004D28A31AAF61C70097A701 /* UIExplorerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 004D28A21AAF61C70097A701 /* UIExplorerTests.m */; }; 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; }; 134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; }; 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; 134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; }; 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; }; - 1353F5461B0E64F9009B4FAC /* ClippingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1353F5451B0E64F9009B4FAC /* ClippingTests.m */; }; 139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 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 */; }; + 141FC1211B222EBB004D5FFB /* IntegrationTestsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 141FC1201B222EBB004D5FFB /* IntegrationTestsTests.m */; }; + 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 */; }; @@ -28,13 +50,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 */; @@ -77,6 +92,13 @@ remoteGlobalIDString = 3C86DF461ADF2C930047B81A; remoteInfo = RCTWebSocket; }; + 143BC59B1B21E3E100462512 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = UIExplorer; + }; 147CED4A1AB34F8C00DA3E4C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */; @@ -122,15 +144,12 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 004D289E1AAF61C70097A701 /* UIExplorerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 004D28A11AAF61C70097A701 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 004D28A21AAF61C70097A701 /* UIExplorerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UIExplorerTests.m; sourceTree = ""; }; + 004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = ""; }; 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; - 1353F5451B0E64F9009B4FAC /* ClippingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClippingTests.m; sourceTree = ""; }; 139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = UIExplorer/AppDelegate.h; sourceTree = ""; }; @@ -140,7 +159,37 @@ 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 = ""; }; + 141FC1201B222EBB004D5FFB /* IntegrationTestsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegrationTestsTests.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 = ""; }; + 143BC5831B21E18100462512 /* testSwitchExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testSwitchExampleSnapshot_1@2x.png"; sourceTree = ""; }; + 143BC5841B21E18100462512 /* testTabBarExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testTabBarExampleSnapshot_1@2x.png"; sourceTree = ""; }; + 143BC5851B21E18100462512 /* testTextExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testTextExampleSnapshot_1@2x.png"; sourceTree = ""; }; + 143BC5861B21E18100462512 /* testViewExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testViewExampleSnapshot_1@2x.png"; sourceTree = ""; }; + 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 = ""; }; @@ -152,6 +201,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; }; @@ -174,27 +236,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 143BC5921B21E3E100462512 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 004D289F1AAF61C70097A701 /* UIExplorerTests */ = { - isa = PBXGroup; - children = ( - 004D28A21AAF61C70097A701 /* UIExplorerTests.m */, - 1353F5451B0E64F9009B4FAC /* ClippingTests.m */, - 004D28A01AAF61C70097A701 /* Supporting Files */, - ); - path = UIExplorerTests; - sourceTree = ""; - }; - 004D28A01AAF61C70097A701 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 004D28A11AAF61C70097A701 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; 1316A21D1AA397F400C0188E /* Libraries */ = { isa = PBXGroup; children = ( @@ -275,6 +326,65 @@ name = UIExplorer; sourceTree = ""; }; + 143BC57C1B21E18100462512 /* UIExplorerUnitTests */ = { + isa = PBXGroup; + children = ( + 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; + sourceTree = ""; + }; + 143BC57F1B21E18100462512 /* ReferenceImages */ = { + isa = PBXGroup; + children = ( + 143BC5801B21E18100462512 /* Examples-UIExplorer-UIExplorerApp */, + ); + path = ReferenceImages; + sourceTree = ""; + }; + 143BC5801B21E18100462512 /* Examples-UIExplorer-UIExplorerApp */ = { + isa = PBXGroup; + children = ( + 143BC5811B21E18100462512 /* testLayoutExampleSnapshot_1@2x.png */, + 143BC5821B21E18100462512 /* testSliderExampleSnapshot_1@2x.png */, + 143BC5831B21E18100462512 /* testSwitchExampleSnapshot_1@2x.png */, + 143BC5841B21E18100462512 /* testTabBarExampleSnapshot_1@2x.png */, + 143BC5851B21E18100462512 /* testTextExampleSnapshot_1@2x.png */, + 143BC5861B21E18100462512 /* testViewExampleSnapshot_1@2x.png */, + ); + path = "Examples-UIExplorer-UIExplorerApp"; + sourceTree = ""; + }; + 143BC5961B21E3E100462512 /* UIExplorerIntegrationTests */ = { + isa = PBXGroup; + children = ( + 141FC1201B222EBB004D5FFB /* IntegrationTestsTests.m */, + 143BC5A01B21E45C00462512 /* UIExplorerIntegrationTests.m */, + 143BC5971B21E3E100462512 /* Supporting Files */, + ); + path = UIExplorerIntegrationTests; + sourceTree = ""; + }; + 143BC5971B21E3E100462512 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 143BC5981B21E3E100462512 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; 147CED471AB34F8C00DA3E4C /* Products */ = { isa = PBXGroup; children = ( @@ -291,6 +401,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 = ( @@ -320,7 +453,9 @@ children = ( 13B07FAE1A68108700A75B9A /* UIExplorer */, 1316A21D1AA397F400C0188E /* Libraries */, - 004D289F1AAF61C70097A701 /* UIExplorerTests */, + 143BC57C1B21E18100462512 /* UIExplorerUnitTests */, + 143BC5961B21E3E100462512 /* UIExplorerIntegrationTests */, + 14D6D6EA1B2205C0001FB087 /* OCMock */, 83CBBA001A601CBA00E9B192 /* Products */, ); indentWidth = 2; @@ -331,7 +466,8 @@ isa = PBXGroup; children = ( 13B07F961A680F5B00A75B9A /* UIExplorer.app */, - 004D289E1AAF61C70097A701 /* UIExplorerTests.xctest */, + 004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */, + 143BC5951B21E3E100462512 /* UIExplorerIntegrationTests.xctest */, ); name = Products; sourceTree = ""; @@ -347,9 +483,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 004D289D1AAF61C70097A701 /* UIExplorerTests */ = { + 004D289D1AAF61C70097A701 /* UIExplorerUnitTests */ = { isa = PBXNativeTarget; - buildConfigurationList = 004D28AD1AAF61C70097A701 /* Build configuration list for PBXNativeTarget "UIExplorerTests" */; + buildConfigurationList = 004D28AD1AAF61C70097A701 /* Build configuration list for PBXNativeTarget "UIExplorerUnitTests" */; buildPhases = ( 004D289A1AAF61C70097A701 /* Sources */, 004D289B1AAF61C70097A701 /* Frameworks */, @@ -358,11 +494,10 @@ buildRules = ( ); dependencies = ( - 004D28A51AAF61C70097A701 /* PBXTargetDependency */, ); - name = UIExplorerTests; + name = UIExplorerUnitTests; productName = UIExplorerTests; - productReference = 004D289E1AAF61C70097A701 /* UIExplorerTests.xctest */; + productReference = 004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 13B07F861A680F5B00A75B9A /* UIExplorer */ = { @@ -382,6 +517,24 @@ productReference = 13B07F961A680F5B00A75B9A /* UIExplorer.app */; productType = "com.apple.product-type.application"; }; + 143BC5941B21E3E100462512 /* UIExplorerIntegrationTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 143BC59D1B21E3E100462512 /* Build configuration list for PBXNativeTarget "UIExplorerIntegrationTests" */; + buildPhases = ( + 143BC5911B21E3E100462512 /* Sources */, + 143BC5921B21E3E100462512 /* Frameworks */, + 143BC5931B21E3E100462512 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 143BC59C1B21E3E100462512 /* PBXTargetDependency */, + ); + name = UIExplorerIntegrationTests; + productName = UIExplorerIntegrationTests; + productReference = 143BC5951B21E3E100462512 /* UIExplorerIntegrationTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -393,6 +546,9 @@ TargetAttributes = { 004D289D1AAF61C70097A701 = { CreatedOnToolsVersion = 6.1.1; + }; + 143BC5941B21E3E100462512 = { + CreatedOnToolsVersion = 6.3.2; TestTargetID = 13B07F861A680F5B00A75B9A; }; }; @@ -461,7 +617,8 @@ projectRoot = ""; targets = ( 13B07F861A680F5B00A75B9A /* UIExplorer */, - 004D289D1AAF61C70097A701 /* UIExplorerTests */, + 004D289D1AAF61C70097A701 /* UIExplorerUnitTests */, + 143BC5941B21E3E100462512 /* UIExplorerIntegrationTests */, ); }; /* End PBXProject section */ @@ -570,6 +727,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 143BC5931B21E3E100462512 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -577,8 +741,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 004D28A31AAF61C70097A701 /* UIExplorerTests.m in Sources */, - 1353F5461B0E64F9009B4FAC /* 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; }; @@ -591,13 +762,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 143BC5911B21E3E100462512 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 141FC1211B222EBB004D5FFB /* IntegrationTestsTests.m in Sources */, + 143BC5A11B21E45C00462512 /* UIExplorerIntegrationTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 004D28A51AAF61C70097A701 /* PBXTargetDependency */ = { + 143BC59C1B21E3E100462512 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 13B07F861A680F5B00A75B9A /* UIExplorer */; - targetProxy = 004D28A41AAF61C70097A701 /* PBXContainerItemProxy */; + targetProxy = 143BC59B1B21E3E100462512 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -617,47 +797,59 @@ 004D28A61AAF61C70097A701 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "FB_REFERENCE_IMAGE_DIR=\"\\\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\\\"\"", - "$(inherited)", - ); + GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; HEADER_SEARCH_PATHS = ( "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../React/**", + "$(SRCROOT)/UIExplorerUnitTests/**", ); - INFOPLIST_FILE = UIExplorerTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + 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 = UIExplorerTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + 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; }; @@ -698,6 +890,47 @@ }; name = Release; }; + 143BC59E1B21E3E100462512 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "FB_REFERENCE_IMAGE_DIR=\"\\\"$(SOURCE_ROOT)/$(PROJECT_NAME)IntegrationTests/ReferenceImages\\\"\"", + ); + INFOPLIST_FILE = UIExplorerIntegrationTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UIExplorer.app/UIExplorer"; + }; + name = Debug; + }; + 143BC59F1B21E3E100462512 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = UIExplorerIntegrationTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UIExplorer.app/UIExplorer"; + }; + name = Release; + }; 83CBBA201A601CBA00E9B192 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -787,7 +1020,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 004D28AD1AAF61C70097A701 /* Build configuration list for PBXNativeTarget "UIExplorerTests" */ = { + 004D28AD1AAF61C70097A701 /* Build configuration list for PBXNativeTarget "UIExplorerUnitTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 004D28A61AAF61C70097A701 /* Debug */, @@ -805,6 +1038,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 143BC59D1B21E3E100462512 /* Build configuration list for PBXNativeTarget "UIExplorerIntegrationTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 143BC59E1B21E3E100462512 /* Debug */, + 143BC59F1B21E3E100462512 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "UIExplorer" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme index 488c0077d..cb55d85b0 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme @@ -29,8 +29,22 @@ + + + + @@ -47,8 +61,18 @@ + + + + diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index 9d3adb2ee..5e707db41 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -36,7 +36,7 @@ * on the same Wi-Fi network. */ - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle?dev=true"]; + jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.includeRequire.runModule.bundle?dev=true"]; /** * OPTION 2 diff --git a/Examples/UIExplorer/UIExplorerApp.android.js b/Examples/UIExplorer/UIExplorerApp.android.js new file mode 100644 index 000000000..69767c4b7 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerApp.android.js @@ -0,0 +1,125 @@ +/** + * 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. + * + * @providesModule UIExplorerApp + * @flow + */ +'use strict'; + +var React = require('react-native'); +var Dimensions = require('Dimensions'); +var DrawerLayoutAndroid = require('DrawerLayoutAndroid'); +var ToolbarAndroid = require('ToolbarAndroid'); +var UIExplorerList = require('./UIExplorerList'); +var { + StyleSheet, + View, +} = React; + +var DRAWER_WIDTH_LEFT = 56; + +var UIExplorerApp = React.createClass({ + + getInitialState: function() { + return { + example: { + title: 'UIExplorer', + component: this._renderHome(), + }, + }; + }, + + render: function() { + return ( + { this.drawer = drawer; }} + renderNavigationView={this._renderNavigationView}> + {this._renderNavigation()} + + ); + }, + + _renderNavigationView: function() { + return ( + + ); + }, + + onSelectExample: function(example) { + this.drawer.closeDrawer(); + if (example.title === 'UIExplorer') { + example.component = this._renderHome(); + } + this.setState({ + example: { + title: example.title, + component: example.component, + }, + }); + }, + + _renderHome: function() { + var onSelectExample = this.onSelectExample; + return React.createClass({ + render: function() { + return ( + + ); + } + }); + }, + + _renderNavigation: function() { + var Component = this.state.example.component; + return ( + + this.drawer.openDrawer()} + style={styles.toolbar} + title={this.state.example.title} + /> + + + ); + }, + +}); + +var styles = StyleSheet.create({ + messageText: { + fontSize: 17, + fontWeight: '500', + padding: 15, + marginTop: 50, + marginLeft: 15, + }, + container: { + flex: 1, + }, + toolbar: { + backgroundColor: '#E9EAED', + height: 56, + }, +}); + +module.exports = UIExplorerApp; diff --git a/Examples/UIExplorer/UIExplorerApp.js b/Examples/UIExplorer/UIExplorerApp.ios.js similarity index 76% rename from Examples/UIExplorer/UIExplorerApp.js rename to Examples/UIExplorer/UIExplorerApp.ios.js index fba5e89e4..fe643dec5 100644 --- a/Examples/UIExplorer/UIExplorerApp.js +++ b/Examples/UIExplorer/UIExplorerApp.ios.js @@ -43,22 +43,22 @@ var UIExplorerApp = React.createClass({ /> ); } - return ( - { - this.setState({ openExternalExample: example, }); - }, - } - }} - itemWrapperStyle={styles.itemWrapper} - tintColor='#008888' - /> - ); + return ( + { + this.setState({ openExternalExample: example, }); + }, + } + }} + itemWrapperStyle={styles.itemWrapper} + tintColor="#008888" + /> + ); } }); diff --git a/IntegrationTests/IntegrationTestsTests/Info.plist b/Examples/UIExplorer/UIExplorerIntegrationTests/Info.plist similarity index 90% rename from IntegrationTests/IntegrationTestsTests/Info.plist rename to Examples/UIExplorer/UIExplorerIntegrationTests/Info.plist index 87e3a6175..802c25416 100644 --- a/IntegrationTests/IntegrationTestsTests/Info.plist +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.$(PRODUCT_NAME:rfc1034identifier) + com.facebook.React.$(PRODUCT_NAME:rfc1034identifier) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/IntegrationTests/IntegrationTestsTests/IntegrationTestsTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTestsTests.m similarity index 88% rename from IntegrationTests/IntegrationTestsTests/IntegrationTestsTests.m rename to Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTestsTests.m index 0aca49d3c..ddd204f99 100644 --- a/IntegrationTests/IntegrationTestsTests/IntegrationTestsTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTestsTests.m @@ -30,10 +30,10 @@ #endif NSString *version = [[UIDevice currentDevice] systemVersion]; RCTAssert([version integerValue] == 8, @"Tests should be run on iOS 8.x, found %@", version); - _runner = RCTInitRunnerForApp(@"IntegrationTests/IntegrationTestsApp"); + _runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp"); // If tests have changes, set recordMode = YES below and run the affected - // tests on an iPhone5, iOS 8.1 simulator. + // tests on an iPhone5, iOS 8.3 simulator. _runner.recordMode = NO; } @@ -71,7 +71,7 @@ [_runner runTest:_cmd module:@"AsyncStorageTest"]; } -- (void)testLayoutEvents +- (void)DISABLED_testLayoutEvents // #7149037 { [_runner runTest:_cmd module:@"LayoutEventsTest"]; } @@ -81,6 +81,11 @@ [_runner runTest:_cmd module:@"AppEventsTest"]; } +- (void)testPromises +{ + [_runner runTest:_cmd module:@"PromiseTest"]; +} + #pragma mark Snapshot Tests - (void)testSimpleSnapshot diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testLayoutExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExampleSnapshot_1@2x.png similarity index 100% rename from Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testLayoutExampleSnapshot_1@2x.png rename to Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExampleSnapshot_1@2x.png diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testSliderExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExampleSnapshot_1@2x.png similarity index 100% rename from Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testSliderExampleSnapshot_1@2x.png rename to Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExampleSnapshot_1@2x.png diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testSwitchExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExampleSnapshot_1@2x.png similarity index 100% rename from Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testSwitchExampleSnapshot_1@2x.png rename to Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExampleSnapshot_1@2x.png diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTabBarExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExampleSnapshot_1@2x.png similarity index 100% rename from Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTabBarExampleSnapshot_1@2x.png rename to Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExampleSnapshot_1@2x.png diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExampleSnapshot_1@2x.png similarity index 100% rename from Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png rename to Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExampleSnapshot_1@2x.png diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testViewExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExampleSnapshot_1@2x.png similarity index 100% rename from Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testViewExampleSnapshot_1@2x.png rename to Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExampleSnapshot_1@2x.png diff --git a/IntegrationTests/IntegrationTestsTests/ReferenceImages/IntegrationTests-IntegrationTestsApp/testSimpleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerIntegrationTests-js-IntegrationTestsApp/testSimpleSnapshot_1@2x.png similarity index 100% rename from IntegrationTests/IntegrationTestsTests/ReferenceImages/IntegrationTests-IntegrationTestsApp/testSimpleSnapshot_1@2x.png rename to Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerIntegrationTests-js-IntegrationTestsApp/testSimpleSnapshot_1@2x.png diff --git a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m similarity index 99% rename from Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m rename to Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m index d48b25ddf..c1c5e5737 100644 --- a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m @@ -39,7 +39,7 @@ #endif NSString *version = [[UIDevice currentDevice] systemVersion]; RCTAssert([version isEqualToString:@"8.3"], @"Snapshot tests should be run on iOS 8.3, found %@", version); - _runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerApp"); + _runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerApp.ios"); // If tests have changes, set recordMode = YES below and run the affected // tests on an iPhone5, iOS 8.1 simulator. diff --git a/IntegrationTests/AppEventsTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/AppEventsTest.js similarity index 100% rename from IntegrationTests/AppEventsTest.js rename to Examples/UIExplorer/UIExplorerIntegrationTests/js/AppEventsTest.js diff --git a/IntegrationTests/AsyncStorageTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/AsyncStorageTest.js similarity index 100% rename from IntegrationTests/AsyncStorageTest.js rename to Examples/UIExplorer/UIExplorerIntegrationTests/js/AsyncStorageTest.js diff --git a/IntegrationTests/IntegrationTestHarnessTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestHarnessTest.js similarity index 100% rename from IntegrationTests/IntegrationTestHarnessTest.js rename to Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestHarnessTest.js diff --git a/IntegrationTests/IntegrationTestsApp.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js similarity index 98% rename from IntegrationTests/IntegrationTestsApp.js rename to Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js index d769c0831..21f0f7a2a 100644 --- a/IntegrationTests/IntegrationTestsApp.js +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js @@ -28,6 +28,7 @@ var TESTS = [ require('./LayoutEventsTest'), require('./AppEventsTest'), require('./SimpleSnapshotTest'), + require('./PromiseTest'), ]; TESTS.forEach( diff --git a/IntegrationTests/LayoutEventsTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/LayoutEventsTest.js similarity index 100% rename from IntegrationTests/LayoutEventsTest.js rename to Examples/UIExplorer/UIExplorerIntegrationTests/js/LayoutEventsTest.js diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/js/PromiseTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/PromiseTest.js new file mode 100644 index 000000000..38660d3d8 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/js/PromiseTest.js @@ -0,0 +1,50 @@ +/** + * 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. + * + * @providesModule PromiseTest + */ +'use strict'; + +var RCTTestModule = require('NativeModules').TestModule; +var React = require('react-native'); + +var PromiseTest = React.createClass({ + + shouldResolve: false, + shouldReject: false, + + componentDidMount() { + Promise.all([ + this.testShouldResolve(), + this.testShouldReject(), + ]).then(() => RCTTestModule.finish( + this.shouldResolve && this.shouldReject + )); + }, + + testShouldResolve() { + return RCTTestModule + .shouldResolve() + .then(() => this.shouldResolve = true) + .catch(() => this.shouldResolve = false); + }, + + testShouldReject() { + return RCTTestModule + .shouldReject() + .then(() => this.shouldReject = false) + .catch(() => this.shouldReject = true); + }, + + render() { + return ; + } + +}); + +module.exports = PromiseTest; diff --git a/IntegrationTests/SimpleSnapshotTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/SimpleSnapshotTest.js similarity index 100% rename from IntegrationTests/SimpleSnapshotTest.js rename to Examples/UIExplorer/UIExplorerIntegrationTests/js/SimpleSnapshotTest.js diff --git a/IntegrationTests/TimersTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/TimersTest.js similarity index 100% rename from IntegrationTests/TimersTest.js rename to Examples/UIExplorer/UIExplorerIntegrationTests/js/TimersTest.js diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index cd73e6d06..43d733506 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -20,6 +20,7 @@ var { AppRegistry, ListView, PixelRatio, + Platform, StyleSheet, Text, TextInput, @@ -34,51 +35,67 @@ import type { ExampleModule } from 'ExampleTypes'; var createExamplePage = require('./createExamplePage'); -var COMPONENTS = [ - require('./ActivityIndicatorIOSExample'), - require('./DatePickerIOSExample'), - require('./ImageExample'), - require('./ListViewExample'), - require('./ListViewPagingExample'), - require('./MapViewExample'), - require('./Navigator/NavigatorExample'), - require('./NavigatorIOSColorsExample'), - require('./NavigatorIOSExample'), - require('./PickerIOSExample'), - require('./ProgressViewIOSExample'), - require('./ScrollViewExample'), - require('./SegmentedControlIOSExample'), - require('./SliderIOSExample'), - require('./SwitchIOSExample'), - require('./TabBarIOSExample'), - require('./TextExample.ios'), - require('./TextInputExample'), - require('./TouchableExample'), +var COMMON_COMPONENTS = [ require('./ViewExample'), require('./WebViewExample'), ]; -var APIS = [ - require('./AccessibilityIOSExample'), - require('./ActionSheetIOSExample'), - require('./AdSupportIOSExample'), - require('./AlertIOSExample'), - require('./AppStateIOSExample'), - require('./AsyncStorageExample'), - require('./BorderExample'), - require('./CameraRollExample.ios'), +var COMMON_APIS = [ require('./GeolocationExample'), - require('./LayoutEventsExample'), require('./LayoutExample'), - require('./NetInfoExample'), require('./PanResponderExample'), - require('./PointerEventsExample'), - require('./PushNotificationIOSExample'), - require('./StatusBarIOSExample'), - require('./TimerExample'), - require('./VibrationIOSExample'), ]; +if (Platform.OS === 'ios') { + var COMPONENTS = COMMON_COMPONENTS.concat([ + require('./ActivityIndicatorIOSExample'), + require('./DatePickerIOSExample'), + require('./ImageExample'), + require('./ListViewExample'), + require('./ListViewPagingExample'), + require('./MapViewExample'), + require('./Navigator/NavigatorExample'), + require('./NavigatorIOSColorsExample'), + require('./NavigatorIOSExample'), + require('./PickerIOSExample'), + require('./ProgressViewIOSExample'), + require('./ScrollViewExample'), + require('./SegmentedControlIOSExample'), + require('./SliderIOSExample'), + require('./SwitchIOSExample'), + require('./TabBarIOSExample'), + require('./TextExample.ios'), + require('./TextInputExample'), + require('./TouchableExample'), + ]); + + var APIS = COMMON_APIS.concat([ + require('./AccessibilityIOSExample'), + require('./ActionSheetIOSExample'), + require('./AdSupportIOSExample'), + require('./AlertIOSExample'), + require('./AppStateIOSExample'), + require('./AsyncStorageExample'), + require('./BorderExample'), + require('./CameraRollExample.ios'), + require('./LayoutEventsExample'), + require('./NetInfoExample'), + require('./PointerEventsExample'), + require('./PushNotificationIOSExample'), + require('./StatusBarIOSExample'), + require('./TimerExample'), + require('./VibrationIOSExample'), + require('./XHRExample'), + ]); + +} else if (Platform.OS === 'android') { + var COMPONENTS = COMMON_COMPONENTS.concat([ + ]); + + var APIS = COMMON_APIS.concat([ + ]); +} + var ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2, sectionHeaderHasChanged: (h1, h2) => h1 !== h2, @@ -114,10 +131,10 @@ COMPONENTS.concat(APIS).forEach((Example) => { type Props = { navigator: Array<{title: string, component: ReactClass}>, onExternalExampleRequested: Function, + onSelectExample: Function, + isInDrawer: bool, }; - - class UIExplorerList extends React.Component { props: Props; @@ -128,7 +145,7 @@ class UIExplorerList extends React.Component { components: COMPONENTS, apis: APIS, }), - searchText: Settings.get('searchText'), + searchText: Platform.OS === 'ios' ? Settings.get('searchText') : '', }; } @@ -137,8 +154,12 @@ class UIExplorerList extends React.Component { } render() { - return ( - + if (Platform.OS === 'ios' || + (Platform.OS === 'android' && !this.props.isInDrawer)) { + var platformTextInputStyle = + Platform.OS === 'ios' ? styles.searchTextInputIOS : + Platform.OS === 'android' ? styles.searchTextInputAndroid : {}; + var textInput = ( - + ); + } + + var homePage; + if (Platform.OS === 'android' && this.props.isInDrawer) { + homePage = this._renderRow({ + title: 'UIExplorer', + description: 'List of examples', + }, -1); + } + + return ( + + {textInput} + {homePage} this._onPressRow(example)}> @@ -205,16 +240,23 @@ class UIExplorerList extends React.Component { Settings.set({searchText: text}); } - _onPressRow(example: ExampleModule) { + _onPressRow(example: any) { if (example.external) { this.props.onExternalExampleRequested(example); return; } var Component = makeRenderable(example); - this.props.navigator.push({ - title: Component.title, - component: Component, - }); + if (Platform.OS === 'ios') { + this.props.navigator.push({ + title: Component.title, + component: Component, + }); + } else if (Platform.OS === 'android') { + this.props.onSelectExample({ + title: Component.title, + component: Component, + }); + } } } @@ -267,9 +309,14 @@ var styles = StyleSheet.create({ borderColor: '#cccccc', borderRadius: 3, borderWidth: 1, - height: 30, paddingLeft: 8, }, + searchTextInputIOS: { + height: 30, + }, + searchTextInputAndroid: { + padding: 2, + }, }); module.exports = UIExplorerList; diff --git a/Examples/UIExplorer/UIExplorerTests/Info.plist b/Examples/UIExplorer/UIExplorerUnitTests/Info.plist similarity index 100% rename from Examples/UIExplorer/UIExplorerTests/Info.plist rename to Examples/UIExplorer/UIExplorerUnitTests/Info.plist 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..7eafce75e --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -0,0 +1,192 @@ +// 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; + } + + RUN_RUNLOOP_WHILE(weakExecutor, 1); + sleep(1); + 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; + } + + RUN_RUNLOOP_WHILE(weakContext, 1); + sleep(1); + 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..390459b1b --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -0,0 +1,187 @@ +// 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 + +RCT_EXPORT_MODULE() + +- (void)setUp {} + +- (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/UIExplorerTests/ClippingTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTClippingTests.m similarity index 100% rename from Examples/UIExplorer/UIExplorerTests/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..1ba3eaf56 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m @@ -0,0 +1,193 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +#import + +#import "RCTContextExecutor.h" +#import "RCTUtils.h" + + +@interface RCTContextExecutorTests : XCTestCase + +@end + +@implementation RCTContextExecutorTests +{ + RCTContextExecutor *_executor; +} + +- (void)setUp +{ + [super setUp]; + _executor = [[RCTContextExecutor alloc] init]; + RCTSetExecutorID(_executor); + [_executor setUp]; +} + +- (void)testNativeLoggingHookExceptionBehavior +{ + 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); + 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 diff --git a/Examples/UIExplorer/XHRExample.js b/Examples/UIExplorer/XHRExample.js new file mode 100644 index 000000000..c5b350c70 --- /dev/null +++ b/Examples/UIExplorer/XHRExample.js @@ -0,0 +1,131 @@ +/** + * 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. + * + * @flow + */ +'use strict'; + +var React = require('react-native'); +var { + ProgressViewIOS, + StyleSheet, + View, + Text, + TouchableHighlight, +} = React; + +class Downloader extends React.Component { + + xhr: XMLHttpRequest; + cancelled: boolean; + + constructor(props) { + super(props); + this.cancelled = false; + this.state = { + downloading: false, + contentSize: 1, + downloaded: 0, + }; + } + + download() { + this.xhr && this.xhr.abort(); + + var xhr = this.xhr || new XMLHttpRequest(); + xhr.onreadystatechange = () => { + if (xhr.readyState === xhr.HEADERS_RECEIVED) { + var contentSize = parseInt(xhr.getResponseHeader('Content-Length'), 10); + this.setState({ + contentSize: contentSize, + downloaded: 0, + }); + } else if (xhr.readyState === xhr.LOADING) { + this.setState({ + downloaded: xhr.responseText.length, + }); + } else if (xhr.readyState === xhr.DONE) { + this.setState({ + downloading: false, + }); + if (this.cancelled) { + this.cancelled = false; + return; + } + if (xhr.status === 200) { + alert('Download complete!'); + } else if (xhr.status !== 0) { + alert('Error: Server returned HTTP status of ' + xhr.status + ' ' + xhr.responseText); + } else { + alert('Error: ' + xhr.responseText); + } + } + }; + xhr.open('GET', 'http://www.gutenberg.org/cache/epub/100/pg100.txt'); + xhr.send(); + this.xhr = xhr; + + this.setState({downloading: true}); + } + + componentWillUnmount() { + this.cancelled = true; + this.xhr && this.xhr.abort(); + } + + render() { + var button = this.state.downloading ? ( + + + Downloading... + + + ) : ( + + + Download 5MB Text File + + + ); + + return ( + + {button} + + + ); + } +} + +exports.framework = 'React'; +exports.title = 'XMLHttpRequest'; +exports.description = 'XMLHttpRequest'; +exports.examples = [{ + title: 'File Download', + render() { + return ; + } +}]; + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + backgroundColor: '#eeeeee', + padding: 10, + }, +}); diff --git a/IntegrationTests/AppDelegate.m b/IntegrationTests/AppDelegate.m deleted file mode 100644 index e5466b235..000000000 --- a/IntegrationTests/AppDelegate.m +++ /dev/null @@ -1,51 +0,0 @@ -/** - * 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 "AppDelegate.h" - -#import "RCTRootView.h" - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - NSURL *jsCodeLocation; - - // Loading JavaScript code - uncomment the one you want. - - // OPTION 1 - // Load from development server. Start the server from the repository root: - // - // $ npm start - // - // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and - // iOS device are on the same Wi-Fi network. - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/IntegrationTests/IntegrationTestsApp.includeRequire.runModule.bundle?dev=true"]; - - // OPTION 2 - // Load from pre-bundled file on disk. To re-generate the static bundle, run - // - // $ curl http://localhost:8081/IntegrationTests/IntegrationTestsApp.includeRequire.runModule.bundle -o main.jsbundle - // - // and uncomment the next following line - // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; - - RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation - moduleName:@"IntegrationTestsApp" - launchOptions:launchOptions]; - - self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = [[UIViewController alloc] init]; - rootViewController.view = rootView; - self.window.rootViewController = rootViewController; - [self.window makeKeyAndVisible]; - return YES; -} - -@end diff --git a/IntegrationTests/Base.lproj/LaunchScreen.xib b/IntegrationTests/Base.lproj/LaunchScreen.xib deleted file mode 100644 index 52cc0828d..000000000 --- a/IntegrationTests/Base.lproj/LaunchScreen.xib +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/Contents.json b/IntegrationTests/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 413d60e76..000000000 --- a/IntegrationTests/Images.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "images" : [ - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "uie_icon@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "uie_icon@2x-1.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "uie_icon@2x-2.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "uie_icon@2x-3.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "uie_icon@2x-5.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "uie_icon@2x-4.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-1.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-1.png deleted file mode 100644 index 08a42699d..000000000 Binary files a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-1.png and /dev/null differ diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-2.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-2.png deleted file mode 100644 index 08a42699d..000000000 Binary files a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-2.png and /dev/null differ diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-3.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-3.png deleted file mode 100644 index 08a42699d..000000000 Binary files a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-3.png and /dev/null differ diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-4.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-4.png deleted file mode 100644 index 08a42699d..000000000 Binary files a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-4.png and /dev/null differ diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-5.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-5.png deleted file mode 100644 index 08a42699d..000000000 Binary files a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-5.png and /dev/null differ diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x.png deleted file mode 100644 index 08a42699d..000000000 Binary files a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x.png and /dev/null differ diff --git a/IntegrationTests/Info.plist b/IntegrationTests/Info.plist deleted file mode 100644 index 245054621..000000000 --- a/IntegrationTests/Info.plist +++ /dev/null @@ -1,42 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - com.facebook.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - NSLocationWhenInUseUsageDescription - You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! - UIViewControllerBasedStatusBarAppearance - - - diff --git a/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj b/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj deleted file mode 100644 index 78c8b97e1..000000000 --- a/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj +++ /dev/null @@ -1,664 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 004D28A31AAF61C70097A701 /* IntegrationTestsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 004D28A21AAF61C70097A701 /* IntegrationTestsTests.m */; }; - 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; - 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 */; }; - 1405E9C21AC3E65600F5BC37 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1405E9C11AC3E63C00F5BC37 /* libReact.a */; }; - 580C37611AB0F61E0015E709 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C374B1AB0F54A0015E709 /* libRCTAdSupport.a */; }; - 580C37621AB0F6260015E709 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C37501AB0F55C0015E709 /* libRCTGeolocation.a */; }; - 580C37631AB0F62C0015E709 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C37551AB0F56E0015E709 /* libRCTImage.a */; }; - 580C37641AB0F6350015E709 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C375A1AB0F5970015E709 /* libRCTNetwork.a */; }; - 580C37651AB0F63E0015E709 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C375F1AB0F5D10015E709 /* libRCTText.a */; }; - 58B80D5F1ABA4147004008FB /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C378F1AB104B00015E709 /* libRCTTest.a */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 1405E9C01AC3E63C00F5BC37 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 1405E9BC1AC3E63C00F5BC37 /* React.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; - remoteInfo = React; - }; - 58005BCB1ABA44F10062E044 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 13B07F861A680F5B00A75B9A; - remoteInfo = IntegrationTests; - }; - 580C374A1AB0F54A0015E709 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; - remoteInfo = RCTAdSupport; - }; - 580C374F1AB0F55C0015E709 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = RCTGeolocation; - }; - 580C37541AB0F56E0015E709 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 58B5115D1A9E6B3D00147676; - remoteInfo = RCTImage; - }; - 580C37591AB0F5970015E709 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 134180261AA91779003F314A /* RCTNetwork.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 58B511DB1A9E6C8500147676; - remoteInfo = RCTNetwork; - }; - 580C375E1AB0F5D10015E709 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 58B5119B1A9E6C1200147676; - remoteInfo = RCTText; - }; - 580C378E1AB104B00015E709 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 580C37891AB104AF0015E709 /* RCTTest.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 580C376F1AB104AF0015E709; - remoteInfo = RCTTest; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 004D289E1AAF61C70097A701 /* IntegrationTestsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IntegrationTestsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 004D28A11AAF61C70097A701 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 004D28A21AAF61C70097A701 /* IntegrationTestsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IntegrationTestsTests.m; sourceTree = ""; }; - 13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; - 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; - 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; - 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = ""; }; - 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../Libraries/GeoLocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; - 13B07F961A680F5B00A75B9A /* IntegrationTests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IntegrationTests.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; - 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; - 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 1405E9BC1AC3E63C00F5BC37 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../React/React.xcodeproj; sourceTree = ""; }; - 580C37891AB104AF0015E709 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 004D289B1AAF61C70097A701 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1405E9C21AC3E65600F5BC37 /* libReact.a in Frameworks */, - 580C37611AB0F61E0015E709 /* libRCTAdSupport.a in Frameworks */, - 580C37621AB0F6260015E709 /* libRCTGeolocation.a in Frameworks */, - 580C37631AB0F62C0015E709 /* libRCTImage.a in Frameworks */, - 580C37641AB0F6350015E709 /* libRCTNetwork.a in Frameworks */, - 58B80D5F1ABA4147004008FB /* libRCTTest.a in Frameworks */, - 580C37651AB0F63E0015E709 /* libRCTText.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 004D289F1AAF61C70097A701 /* IntegrationTestsTests */ = { - isa = PBXGroup; - children = ( - 004D28A21AAF61C70097A701 /* IntegrationTestsTests.m */, - 004D28A01AAF61C70097A701 /* Supporting Files */, - ); - path = IntegrationTestsTests; - sourceTree = ""; - }; - 004D28A01AAF61C70097A701 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 004D28A11AAF61C70097A701 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 1316A21D1AA397F400C0188E /* Libraries */ = { - isa = PBXGroup; - children = ( - 1405E9BC1AC3E63C00F5BC37 /* React.xcodeproj */, - 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */, - 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */, - 13417FE31AA91428003F314A /* RCTImage.xcodeproj */, - 134180261AA91779003F314A /* RCTNetwork.xcodeproj */, - 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */, - 580C37891AB104AF0015E709 /* RCTTest.xcodeproj */, - ); - name = Libraries; - sourceTree = ""; - }; - 13B07FAE1A68108700A75B9A /* IntegrationTests */ = { - isa = PBXGroup; - children = ( - 13B07FAF1A68108700A75B9A /* AppDelegate.h */, - 13B07FB01A68108700A75B9A /* AppDelegate.m */, - 13B07FB51A68108700A75B9A /* Images.xcassets */, - 13B07FB61A68108700A75B9A /* Info.plist */, - 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, - 13B07FB71A68108700A75B9A /* main.m */, - ); - name = IntegrationTests; - sourceTree = ""; - }; - 1405E9BD1AC3E63C00F5BC37 /* Products */ = { - isa = PBXGroup; - children = ( - 1405E9C11AC3E63C00F5BC37 /* libReact.a */, - ); - name = Products; - sourceTree = ""; - }; - 580C37471AB0F54A0015E709 /* Products */ = { - isa = PBXGroup; - children = ( - 580C374B1AB0F54A0015E709 /* libRCTAdSupport.a */, - ); - name = Products; - sourceTree = ""; - }; - 580C374C1AB0F55C0015E709 /* Products */ = { - isa = PBXGroup; - children = ( - 580C37501AB0F55C0015E709 /* libRCTGeolocation.a */, - ); - name = Products; - sourceTree = ""; - }; - 580C37511AB0F56E0015E709 /* Products */ = { - isa = PBXGroup; - children = ( - 580C37551AB0F56E0015E709 /* libRCTImage.a */, - ); - name = Products; - sourceTree = ""; - }; - 580C37561AB0F5970015E709 /* Products */ = { - isa = PBXGroup; - children = ( - 580C375A1AB0F5970015E709 /* libRCTNetwork.a */, - ); - name = Products; - sourceTree = ""; - }; - 580C375B1AB0F5D10015E709 /* Products */ = { - isa = PBXGroup; - children = ( - 580C375F1AB0F5D10015E709 /* libRCTText.a */, - ); - name = Products; - sourceTree = ""; - }; - 580C378A1AB104AF0015E709 /* Products */ = { - isa = PBXGroup; - children = ( - 580C378F1AB104B00015E709 /* libRCTTest.a */, - ); - name = Products; - sourceTree = ""; - }; - 83CBB9F61A601CBA00E9B192 = { - isa = PBXGroup; - children = ( - 13B07FAE1A68108700A75B9A /* IntegrationTests */, - 1316A21D1AA397F400C0188E /* Libraries */, - 004D289F1AAF61C70097A701 /* IntegrationTestsTests */, - 83CBBA001A601CBA00E9B192 /* Products */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - }; - 83CBBA001A601CBA00E9B192 /* Products */ = { - isa = PBXGroup; - children = ( - 13B07F961A680F5B00A75B9A /* IntegrationTests.app */, - 004D289E1AAF61C70097A701 /* IntegrationTestsTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 004D289D1AAF61C70097A701 /* IntegrationTestsTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 004D28AD1AAF61C70097A701 /* Build configuration list for PBXNativeTarget "IntegrationTestsTests" */; - buildPhases = ( - 004D289A1AAF61C70097A701 /* Sources */, - 004D289B1AAF61C70097A701 /* Frameworks */, - 004D289C1AAF61C70097A701 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 58005BCC1ABA44F10062E044 /* PBXTargetDependency */, - ); - name = IntegrationTestsTests; - productName = IntegrationTestsTests; - productReference = 004D289E1AAF61C70097A701 /* IntegrationTestsTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 13B07F861A680F5B00A75B9A /* IntegrationTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "IntegrationTests" */; - buildPhases = ( - 13B07F871A680F5B00A75B9A /* Sources */, - 13B07F8C1A680F5B00A75B9A /* Frameworks */, - 13B07F8E1A680F5B00A75B9A /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = IntegrationTests; - productName = "Hello World"; - productReference = 13B07F961A680F5B00A75B9A /* IntegrationTests.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 83CBB9F71A601CBA00E9B192 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0610; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 004D289D1AAF61C70097A701 = { - CreatedOnToolsVersion = 6.1.1; - TestTargetID = 13B07F861A680F5B00A75B9A; - }; - }; - }; - buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "IntegrationTests" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 83CBB9F61A601CBA00E9B192; - productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; - projectDirPath = ""; - projectReferences = ( - { - ProductGroup = 580C37471AB0F54A0015E709 /* Products */; - ProjectRef = 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */; - }, - { - ProductGroup = 580C374C1AB0F55C0015E709 /* Products */; - ProjectRef = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */; - }, - { - ProductGroup = 580C37511AB0F56E0015E709 /* Products */; - ProjectRef = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */; - }, - { - ProductGroup = 580C37561AB0F5970015E709 /* Products */; - ProjectRef = 134180261AA91779003F314A /* RCTNetwork.xcodeproj */; - }, - { - ProductGroup = 580C378A1AB104AF0015E709 /* Products */; - ProjectRef = 580C37891AB104AF0015E709 /* RCTTest.xcodeproj */; - }, - { - ProductGroup = 580C375B1AB0F5D10015E709 /* Products */; - ProjectRef = 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */; - }, - { - ProductGroup = 1405E9BD1AC3E63C00F5BC37 /* Products */; - ProjectRef = 1405E9BC1AC3E63C00F5BC37 /* React.xcodeproj */; - }, - ); - projectRoot = ""; - targets = ( - 13B07F861A680F5B00A75B9A /* IntegrationTests */, - 004D289D1AAF61C70097A701 /* IntegrationTestsTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXReferenceProxy section */ - 1405E9C11AC3E63C00F5BC37 /* libReact.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libReact.a; - remoteRef = 1405E9C01AC3E63C00F5BC37 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 580C374B1AB0F54A0015E709 /* libRCTAdSupport.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTAdSupport.a; - remoteRef = 580C374A1AB0F54A0015E709 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 580C37501AB0F55C0015E709 /* libRCTGeolocation.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTGeolocation.a; - remoteRef = 580C374F1AB0F55C0015E709 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 580C37551AB0F56E0015E709 /* libRCTImage.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTImage.a; - remoteRef = 580C37541AB0F56E0015E709 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 580C375A1AB0F5970015E709 /* libRCTNetwork.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTNetwork.a; - remoteRef = 580C37591AB0F5970015E709 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 580C375F1AB0F5D10015E709 /* libRCTText.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTText.a; - remoteRef = 580C375E1AB0F5D10015E709 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 580C378F1AB104B00015E709 /* libRCTTest.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTTest.a; - remoteRef = 580C378E1AB104B00015E709 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - -/* Begin PBXResourcesBuildPhase section */ - 004D289C1AAF61C70097A701 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 13B07F8E1A680F5B00A75B9A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, - 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 004D289A1AAF61C70097A701 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 004D28A31AAF61C70097A701 /* IntegrationTestsTests.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 13B07F871A680F5B00A75B9A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, - 13B07FC11A68108700A75B9A /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 58005BCC1ABA44F10062E044 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 13B07F861A680F5B00A75B9A /* IntegrationTests */; - targetProxy = 58005BCB1ABA44F10062E044 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { - isa = PBXVariantGroup; - children = ( - 13B07FB21A68108700A75B9A /* Base */, - ); - name = LaunchScreen.xib; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 004D28A61AAF61C70097A701 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(SDKROOT)/Developer/Library/Frameworks", - "$(DEVELOPER_FRAMEWORKS_DIR)", - ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "FB_REFERENCE_IMAGE_DIR=\"\\\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\\\"\"", - "$(inherited)", - ); - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../React/**", - ); - INFOPLIST_FILE = IntegrationTestsTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/IntegrationTests.app/IntegrationTests"; - }; - name = Debug; - }; - 004D28A71AAF61C70097A701 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(SDKROOT)/Developer/Library/Frameworks", - "$(DEVELOPER_FRAMEWORKS_DIR)", - ); - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../React/**", - ); - INFOPLIST_FILE = IntegrationTestsTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/IntegrationTests.app/IntegrationTests"; - }; - name = Release; - }; - 13B07F941A680F5B00A75B9A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../React/**", - ); - INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = IntegrationTests; - }; - name = Debug; - }; - 13B07F951A680F5B00A75B9A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../React/**", - ); - INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = IntegrationTests; - }; - name = Release; - }; - 83CBBA201A601CBA00E9B192 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../React/**", - ); - IPHONEOS_DEPLOYMENT_TARGET = 7.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 83CBBA211A601CBA00E9B192 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../React/**", - ); - IPHONEOS_DEPLOYMENT_TARGET = 7.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 004D28AD1AAF61C70097A701 /* Build configuration list for PBXNativeTarget "IntegrationTestsTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 004D28A61AAF61C70097A701 /* Debug */, - 004D28A71AAF61C70097A701 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "IntegrationTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 13B07F941A680F5B00A75B9A /* Debug */, - 13B07F951A680F5B00A75B9A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "IntegrationTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 83CBBA201A601CBA00E9B192 /* Debug */, - 83CBBA211A601CBA00E9B192 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; -} diff --git a/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js b/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js index dfc09ba7c..3243fb145 100644 --- a/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js +++ b/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js @@ -19,9 +19,17 @@ var slice = Array.prototype.slice; var MethodTypes = keyMirror({ remote: null, + remoteAsync: null, local: null, }); +type ErrorData = { + message: string; + domain: string; + code: number; + nativeStackIOS?: string; +}; + /** * Creates remotely invokable modules. */ @@ -36,21 +44,37 @@ var BatchedBridgeFactory = { */ _createBridgedModule: function(messageQueue, moduleConfig, moduleName) { var remoteModule = mapObject(moduleConfig.methods, function(methodConfig, memberName) { - return methodConfig.type === MethodTypes.local ? null : function() { - var lastArg = arguments.length > 0 ? arguments[arguments.length - 1] : null; - var secondLastArg = arguments.length > 1 ? arguments[arguments.length - 2] : null; - var hasSuccCB = typeof lastArg === 'function'; - var hasErrorCB = typeof secondLastArg === 'function'; - hasErrorCB && invariant( - hasSuccCB, - 'Cannot have a non-function arg after a function arg.' - ); - var numCBs = (hasSuccCB ? 1 : 0) + (hasErrorCB ? 1 : 0); - var args = slice.call(arguments, 0, arguments.length - numCBs); - var onSucc = hasSuccCB ? lastArg : null; - var onFail = hasErrorCB ? secondLastArg : null; - return messageQueue.call(moduleName, memberName, args, onFail, onSucc); - }; + switch (methodConfig.type) { + case MethodTypes.remoteAsync: + return function(...args) { + return new Promise((resolve, reject) => { + messageQueue.call(moduleName, memberName, args, resolve, (errorData) => { + var error = _createErrorFromErrorData(errorData); + reject(error); + }); + }); + }; + + case MethodTypes.local: + return null; + + default: + return function() { + var lastArg = arguments.length > 0 ? arguments[arguments.length - 1] : null; + var secondLastArg = arguments.length > 1 ? arguments[arguments.length - 2] : null; + var hasSuccCB = typeof lastArg === 'function'; + var hasErrorCB = typeof secondLastArg === 'function'; + hasErrorCB && invariant( + hasSuccCB, + 'Cannot have a non-function arg after a function arg.' + ); + var numCBs = (hasSuccCB ? 1 : 0) + (hasErrorCB ? 1 : 0); + var args = slice.call(arguments, 0, arguments.length - numCBs); + var onSucc = hasSuccCB ? lastArg : null; + var onFail = hasErrorCB ? secondLastArg : null; + return messageQueue.call(moduleName, memberName, args, onFail, onSucc); + }; + } }); for (var constName in moduleConfig.constants) { warning(!remoteModule[constName], 'saw constant and method named %s', constName); @@ -59,7 +83,6 @@ var BatchedBridgeFactory = { return remoteModule; }, - create: function(MessageQueue, modulesConfig, localModulesConfig) { var messageQueue = new MessageQueue(modulesConfig, localModulesConfig); return { @@ -80,4 +103,14 @@ var BatchedBridgeFactory = { } }; +function _createErrorFromErrorData(errorData: ErrorData): Error { + var { + message, + ...extraErrorInfo, + } = errorData; + var error = new Error(message); + error.framesToPop = 1; + return Object.assign(error, extraErrorInfo); +} + module.exports = BatchedBridgeFactory; diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 7d3f04b33..0b3d8d4e3 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -19,7 +19,6 @@ var Platform = require('Platform'); var PropTypes = require('ReactPropTypes'); var React = require('React'); var ReactChildren = require('ReactChildren'); -var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheet = require('StyleSheet'); var Text = require('Text'); var TextInputState = require('TextInputState'); @@ -29,36 +28,7 @@ var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); var createReactNativeComponentClass = require('createReactNativeComponentClass'); var emptyFunction = require('emptyFunction'); var invariant = require('invariant'); -var merge = require('merge'); - -var RCTTextViewAttributes = merge(ReactNativeViewAttributes.UIView, { - autoCorrect: true, - autoCapitalize: true, - clearTextOnFocus: true, - color: true, - editable: true, - fontFamily: true, - fontSize: true, - fontStyle: true, - fontWeight: true, - keyboardType: true, - returnKeyType: true, - enablesReturnKeyAutomatically: true, - secureTextEntry: true, - selectTextOnFocus: true, - mostRecentEventCounter: true, - placeholder: true, - placeholderTextColor: true, - text: true, -}); - -var RCTTextFieldAttributes = merge(RCTTextViewAttributes, { - caretHidden: true, - enabled: true, - clearButtonMode: true, - clearTextOnFocus: true, - selectTextOnFocus: true, -}); +var requireNativeComponent = require('requireNativeComponent'); var onlyMultiline = { onSelectionChange: true, @@ -82,16 +52,14 @@ var AndroidTextInputAttributes = { testID: true, }; -var viewConfigIOS = { - uiViewClassName: 'RCTTextField', - validAttributes: RCTTextFieldAttributes, -}; - var viewConfigAndroid = { uiViewClassName: 'AndroidTextInput', validAttributes: AndroidTextInputAttributes, }; +var RCTTextView = requireNativeComponent('RCTTextView', null); +var RCTTextField = requireNativeComponent('RCTTextField', null); + type DefaultProps = { bufferDelay: number; }; @@ -164,7 +132,7 @@ var TextInput = React.createClass({ */ keyboardType: PropTypes.oneOf([ // Cross-platform - 'default', + 'default', 'numeric', 'email-address', // iOS-only @@ -296,7 +264,7 @@ var TextInput = React.createClass({ */ mixins: [NativeMethodsMixin, TimerMixin], - viewConfig: ((Platform.OS === 'ios' ? viewConfigIOS : + viewConfig: ((Platform.OS === 'ios' ? RCTTextField.viewConfig : (Platform.OS === 'android' ? viewConfigAndroid : {})) : Object), isFocused: function(): boolean { @@ -510,6 +478,7 @@ var TextInput = React.createClass({ onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} + onTextInput={this._onTextInput} onEndEditing={this.props.onEndEditing} onSubmitEditing={this.props.onSubmitEditing} onLayout={this.props.onLayout} @@ -576,16 +545,6 @@ var styles = StyleSheet.create({ }, }); -var RCTTextView = createReactNativeComponentClass({ - validAttributes: RCTTextViewAttributes, - uiViewClassName: 'RCTTextView', -}); - -var RCTTextField = createReactNativeComponentClass({ - validAttributes: RCTTextFieldAttributes, - uiViewClassName: 'RCTTextField', -}); - var AndroidTextInput = createReactNativeComponentClass({ validAttributes: AndroidTextInputAttributes, uiViewClassName: 'AndroidTextInput', diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 56bd98c73..4852e0a47 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -1290,7 +1290,7 @@ var Navigator = React.createClass({ key={this.state.idStack[i]} ref={'scene_' + i} onStartShouldSetResponderCapture={() => { - return !!this.state.transitionFromIndex || !!this.state.activeGesture; + return (this.state.transitionFromIndex != null) || (this.state.transitionFromIndex != null); }} style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}> {React.cloneElement(child, { diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js index a43f2dafd..4b8724cbf 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js +++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js @@ -164,7 +164,6 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({ renderToHardwareTextureAndroid: renderToHardwareTexture, }; - this.refs['crumb_' + index].setNativeProps(props); this.refs['icon_' + index].setNativeProps(props); this.refs['separator_' + index].setNativeProps(props); this.refs['title_' + index].setNativeProps(props); diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj index 3431def50..667995586 100644 --- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj +++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */; }; 1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; }; 1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; }; + 1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1345A8381B26592900583190 /* RCTImageRequestHandler.m */; }; 143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; }; 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; }; 58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; }; @@ -36,6 +37,8 @@ 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImageManager.m; sourceTree = ""; }; 1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImage.h; sourceTree = ""; }; 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = ""; }; + 1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = ""; }; + 1345A8381B26592900583190 /* RCTImageRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageRequestHandler.m; sourceTree = ""; }; 143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = ""; }; 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = ""; }; 143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = ""; }; @@ -71,6 +74,8 @@ 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */, 58B511891A9E6BD600147676 /* RCTImageDownloader.h */, 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */, + 1345A8371B26592900583190 /* RCTImageRequestHandler.h */, + 1345A8381B26592900583190 /* RCTImageRequestHandler.m */, 58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */, 58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */, 58B5118D1A9E6BD600147676 /* RCTNetworkImageViewManager.h */, @@ -152,6 +157,7 @@ 58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */, 58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */, 1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */, + 1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */, 58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */, 1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */, 143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */, diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index 7ff8c6379..760fce614 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -82,8 +82,8 @@ static NSString *RCTCacheKeyForURL(NSURL *url) RCTImageDownloader *strongSelf = weakSelf; NSArray *blocks = strongSelf->_pendingBlocks[cacheKey]; [strongSelf->_pendingBlocks removeObjectForKey:cacheKey]; - for (RCTCachedDataDownloadBlock cacheDownloadBlock in blocks) { - cacheDownloadBlock(cached, data, error); + for (RCTCachedDataDownloadBlock downloadBlock in blocks) { + downloadBlock(cached, data, error); } }); }; diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 7525b37d0..f28502d7e 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -16,9 +16,22 @@ #import #import "RCTConvert.h" +#import "RCTDefines.h" #import "RCTGIFImage.h" #import "RCTImageDownloader.h" #import "RCTLog.h" +#import "RCTUtils.h" + +static void RCTDispatchCallbackOnMainQueue(void (^ __nonnull callback)(NSError *, id), NSError *error, UIImage *image) +{ + if ([NSThread isMainThread]) { + callback(error, image); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(error, image); + }); + } +} static dispatch_queue_t RCTImageLoaderQueue(void) { @@ -31,24 +44,6 @@ static dispatch_queue_t RCTImageLoaderQueue(void) return queue; } -static NSError *RCTErrorWithMessage(NSString *message) -{ - NSDictionary *errorInfo = @{NSLocalizedDescriptionKey: message}; - NSError *error = [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo]; - return error; -} - -static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSError *error, UIImage *image) -{ - if ([NSThread isMainThread]) { - callback(error, image); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - callback(error, image); - }); - } -} - @implementation RCTImageLoader + (ALAssetsLibrary *)assetsLibrary diff --git a/IntegrationTests/AppDelegate.h b/Libraries/Image/RCTImageRequestHandler.h similarity index 70% rename from IntegrationTests/AppDelegate.h rename to Libraries/Image/RCTImageRequestHandler.h index 98876c3b7..0f0359885 100644 --- a/IntegrationTests/AppDelegate.h +++ b/Libraries/Image/RCTImageRequestHandler.h @@ -7,11 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#import "RCTURLRequestHandler.h" -@interface AppDelegate : UIResponder - -@property (nonatomic, strong) UIWindow *window; +@interface RCTImageRequestHandler : NSObject @end - diff --git a/Libraries/Image/RCTImageRequestHandler.m b/Libraries/Image/RCTImageRequestHandler.m new file mode 100644 index 000000000..e5eb3bfd4 --- /dev/null +++ b/Libraries/Image/RCTImageRequestHandler.m @@ -0,0 +1,62 @@ +// +// RCTImageRequestHandler.m +// RCTImage +// +// Created by Nick Lockwood on 09/06/2015. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import "RCTImageRequestHandler.h" + +#import + +#import "RCTImageLoader.h" +#import "RCTUtils.h" + +@implementation RCTImageRequestHandler +{ + NSInteger _currentToken; +} + +RCT_EXPORT_MODULE() + +- (BOOL)canHandleRequest:(NSURLRequest *)request +{ + return [@[@"assets-library", @"ph"] containsObject:[request.URL.scheme lowercaseString]]; +} + +- (id)sendRequest:(NSURLRequest *)request + withDelegate:(id)delegate +{ + NSNumber *requestToken = @(++_currentToken); + NSString *URLString = [request.URL absoluteString]; + [RCTImageLoader loadImageWithTag:URLString callback:^(NSError *error, UIImage *image) { + if (error) { + [delegate URLRequest:requestToken didCompleteWithError:error]; + return; + } + + NSString *mimeType = nil; + NSData *imageData = nil; + if (RCTImageHasAlpha(image.CGImage)) { + mimeType = @"image/png"; + imageData = UIImagePNGRepresentation(image); + } else { + mimeType = @"image/jpeg"; + imageData = UIImageJPEGRepresentation(image, 1.0); + } + + NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL + MIMEType:mimeType + expectedContentLength:imageData.length + textEncodingName:nil]; + + [delegate URLRequest:requestToken didReceiveResponse:response]; + [delegate URLRequest:requestToken didReceiveData:imageData]; + [delegate URLRequest:requestToken didCompleteWithError:nil]; + }]; + + return requestToken; +} + +@end diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 81978ee0c..e87f44fca 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -85,7 +85,7 @@ function setUpAlert() { var alertOpts = { title: 'Alert', message: '' + text, - buttons: [{'cancel': 'Okay'}], + buttons: [{'cancel': 'OK'}], }; RCTAlertManager.alertWithArgs(alertOpts, null); }; @@ -102,6 +102,7 @@ function setUpXHR() { // The native XMLHttpRequest in Chrome dev tools is CORS aware and won't // let you fetch anything from the internet GLOBAL.XMLHttpRequest = require('XMLHttpRequest'); + GLOBAL.FormData = require('FormData'); var fetchPolyfill = require('fetch'); GLOBAL.fetch = fetchPolyfill.fetch; diff --git a/Libraries/LinkingIOS/LinkingIOS.js b/Libraries/LinkingIOS/LinkingIOS.js index 7bd5c008b..84960ff32 100644 --- a/Libraries/LinkingIOS/LinkingIOS.js +++ b/Libraries/LinkingIOS/LinkingIOS.js @@ -13,9 +13,10 @@ var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); var RCTLinkingManager = require('NativeModules').LinkingManager; +var Map = require('Map'); var invariant = require('invariant'); -var _notifHandlers = {}; +var _notifHandlers = new Map(); var _initialURL = RCTLinkingManager && RCTLinkingManager.initialURL; @@ -80,10 +81,6 @@ var DEVICE_NOTIF_EVENT = 'openURL'; * } * }); * ``` - * - * _The iOS simulator does not support the `mailto:` and `tel:` schemas - * because the Mail and Phone apps are not installed - you will need to test - * them on a device._ */ class LinkingIOS { /** @@ -95,10 +92,11 @@ class LinkingIOS { type === 'url', 'LinkingIOS only supports `url` events' ); - _notifHandlers[handler] = RCTDeviceEventEmitter.addListener( + var listener = RCTDeviceEventEmitter.addListener( DEVICE_NOTIF_EVENT, handler ); + _notifHandlers.set(handler, listener); } /** @@ -109,11 +107,12 @@ class LinkingIOS { type === 'url', 'LinkingIOS only supports `url` events' ); - if (!_notifHandlers[handler]) { + var listener = _notifHandlers.get(handler); + if (!listener) { return; } - _notifHandlers[handler].remove(); - _notifHandlers[handler] = null; + listener.remove(); + _notifHandlers.delete(handler); } /** diff --git a/Libraries/Network/FormData.js b/Libraries/Network/FormData.js new file mode 100644 index 000000000..6e44f6193 --- /dev/null +++ b/Libraries/Network/FormData.js @@ -0,0 +1,67 @@ +/** + * 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. + * + * @providesModule FormData + * @flow + */ +'use strict'; + +type FormDataValue = any; +type FormDataPart = [string, FormDataValue]; + +/** + * Polyfill for XMLHttpRequest2 FormData API, allowing multipart POST requests + * with mixed data (string, native files) to be submitted via XMLHttpRequest. + */ +class FormData { + _parts: Array; + _partsByKey: {[key: string]: FormDataPart}; + + constructor() { + this._parts = []; + this._partsByKey = {}; + } + + append(key: string, value: FormDataValue) { + var parts = this._partsByKey[key]; + if (parts) { + // It's a bit unclear what the behaviour should be in this case. + // The XMLHttpRequest spec doesn't specify it, while MDN says that + // the any new values should appended to existing values. We're not + // doing that for now -- it's tedious and doesn't seem worth the effort. + parts[1] = value; + return; + } + parts = [key, value]; + this._parts.push(parts); + this._partsByKey[key] = parts; + } + + getParts(): Array { + return this._parts.map(([name, value]) => { + if (typeof value === 'string') { + return { + string: value, + headers: { + 'content-disposition': 'form-data; name="' + name + '"', + }, + }; + } + var contentDisposition = 'form-data; name="' + name + '"'; + if (typeof value.name === 'string') { + contentDisposition += '; filename="' + value.name + '"'; + } + return { + ...value, + headers: {'content-disposition': contentDisposition}, + }; + }); + } +} + +module.exports = FormData; diff --git a/Libraries/Network/RCTDataManager.m b/Libraries/Network/RCTDataManager.m index f4497a187..a05e942fb 100644 --- a/Libraries/Network/RCTDataManager.m +++ b/Libraries/Network/RCTDataManager.m @@ -11,70 +11,451 @@ #import "RCTAssert.h" #import "RCTConvert.h" +#import "RCTURLRequestHandler.h" +#import "RCTEventDispatcher.h" +#import "RCTHTTPRequestHandler.h" #import "RCTLog.h" #import "RCTUtils.h" -@implementation RCTDataManager +typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result); -RCT_EXPORT_MODULE() +@interface RCTDataManager () + +- (void)processDataForHTTPQuery:(NSDictionary *)data callback:(void (^)(NSError *error, NSDictionary *result))callback; + +@end /** - * Executes a network request. - * The responseSender block won't be called on same thread as called. + * Helper to convert FormData payloads into multipart/formdata requests. */ -RCT_EXPORT_METHOD(queryData:(NSString *)queryType - withQuery:(NSDictionary *)query - responseSender:(RCTResponseSenderBlock)responseSender) +@interface RCTHTTPFormDataHelper : NSObject + +@property (nonatomic, weak) RCTDataManager *dataManager; + +@end + +@implementation RCTHTTPFormDataHelper { - if ([queryType isEqualToString:@"http"]) { + NSMutableArray *parts; + NSMutableData *multipartBody; + RCTHTTPQueryResult _callback; + NSString *boundary; +} - // Build request - NSURL *URL = [RCTConvert NSURL:query[@"url"]]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; - request.HTTPMethod = [RCTConvert NSString:query[@"method"]] ?: @"GET"; - request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]]; - request.HTTPBody = [RCTConvert NSData:query[@"data"]]; +- (void)process:(NSArray *)formData callback:(nonnull void (^)(NSError *error, NSDictionary *result))callback +{ + if (![formData count]) { + callback(nil, nil); + return; + } + parts = [formData mutableCopy]; + _callback = callback; + multipartBody = [[NSMutableData alloc] init]; + boundary = [self generateBoundary]; - // Build data task - NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *connectionError) { + NSDictionary *currentPart = [parts objectAtIndex: 0]; + [_dataManager processDataForHTTPQuery:currentPart callback:^(NSError *e, NSDictionary *r) { + [self handleResult:r error:e]; + }]; +} - NSHTTPURLResponse *httpResponse = nil; - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - // Might be a local file request - httpResponse = (NSHTTPURLResponse *)response; - } +- (NSString *)generateBoundary +{ + NSString *const boundaryChars = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_./"; + const NSUInteger boundaryLength = 70; - // Build response - NSArray *responseJSON; - if (connectionError == nil) { - NSStringEncoding encoding = NSUTF8StringEncoding; - if (response.textEncodingName) { - CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); - encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); - } - responseJSON = @[ - @(httpResponse.statusCode ?: 200), - httpResponse.allHeaderFields ?: @{}, - [[NSString alloc] initWithData:data encoding:encoding] ?: @"", - ]; - } else { - responseJSON = @[ - @(httpResponse.statusCode), - httpResponse.allHeaderFields ?: @{}, - connectionError.localizedDescription ?: [NSNull null], - ]; - } + NSMutableString *output = [NSMutableString stringWithCapacity:boundaryLength]; + NSUInteger numchars = [boundaryChars length]; + for (NSUInteger i = 0; i < boundaryLength; i++) { + [output appendFormat:@"%C", [boundaryChars characterAtIndex:arc4random_uniform((u_int32_t)numchars)]]; + } + return output; +} - // Send response (won't be sent on same thread as caller) - responseSender(responseJSON); +- (void)handleResult:(NSDictionary *)result error:(NSError *)error +{ + if (error) { + _callback(error, nil); + return; + } + NSDictionary *currentPart = parts[0]; + [parts removeObjectAtIndex:0]; + // Start with boundary. + [multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] + dataUsingEncoding:NSUTF8StringEncoding]]; + + // Print headers. + NSMutableDictionary *headers = [(NSDictionary*)currentPart[@"headers"] mutableCopy]; + NSString *partContentType = result[@"contentType"]; + if (partContentType != nil) { + [headers setObject:partContentType forKey:@"content-type"]; + } + [headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) { + [multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue] + dataUsingEncoding:NSUTF8StringEncoding]]; + }]; + + // Add the body. + [multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [multipartBody appendData:result[@"body"]]; + [multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + + if ([parts count]) { + NSDictionary *nextPart = [parts objectAtIndex: 0]; + [_dataManager processDataForHTTPQuery:nextPart callback:^(NSError *e, NSDictionary *r) { + [self handleResult:r error:e]; }]; + return; + } - [task resume]; + // We've processed the last item. Finish and return. + [multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] + dataUsingEncoding:NSUTF8StringEncoding]]; + NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundary]; + _callback(nil, @{@"body": multipartBody, @"contentType": contentType}); +} - } else { +@end - RCTLogError(@"unsupported query type %@", queryType); +/** + * Helper to package in-flight requests together with their response data. + */ +@interface RCTActiveURLRequest : NSObject + +@property (nonatomic, strong) NSNumber *requestID; +@property (nonatomic, strong) NSURLRequest *request; +@property (nonatomic, strong) id handler; +@property (nonatomic, assign) BOOL incrementalUpdates; +@property (nonatomic, strong) NSURLResponse *response; +@property (nonatomic, strong) NSMutableData *data; + +@end + +@implementation RCTActiveURLRequest + +- (void)setResponse:(NSURLResponse *)response; +{ + _response = response; + if (!_incrementalUpdates) { + _data = [[NSMutableData alloc] initWithCapacity:(NSUInteger)MAX(0, response.expectedContentLength)]; + } +} + +@end + +/** + * Helper to load request body data using a handler. + */ +@interface RCTDataLoader : NSObject + +@end + +typedef void (^RCTDataLoaderCallback)(NSData *data, NSString *MIMEType, NSError *error); + +@implementation RCTDataLoader +{ + RCTDataLoaderCallback _callback; + RCTActiveURLRequest *_request; + id _requestToken; +} + +- (instancetype)initWithRequest:(NSURLRequest *)request + handler:(id)handler + callback:(RCTDataLoaderCallback)callback +{ + if ((self = [super init])) { + _callback = callback; + _request = [[RCTActiveURLRequest alloc] init]; + _request.request = request; + _request.handler = handler; + _request.incrementalUpdates = NO; + _requestToken = [handler sendRequest:request withDelegate:self]; + } + return self; +} + +- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response +{ + RCTAssert([requestToken isEqual:_requestToken], @"Shouldn't ever happen"); + _request.response = response; +} + +- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data +{ + RCTAssert([requestToken isEqual:_requestToken], @"Shouldn't ever happen"); + [_request.data appendData:data]; +} + +- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error +{ + RCTAssert(_callback != nil, @"The callback property must be set"); + _callback(_request.data, _request.response.MIMEType, error); +} + +@end + +/** + * Bridge module that provides the JS interface to the network stack. + */ +@implementation RCTDataManager +{ + NSInteger _currentRequestID; + NSMapTable *_activeRequests; +} + +@synthesize bridge = _bridge; + +RCT_EXPORT_MODULE() + +- (instancetype)init +{ + if ((self = [super init])) { + _currentRequestID = 0; + _activeRequests = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory + valueOptions:NSPointerFunctionsStrongMemory + capacity:0]; + } + return self; +} + +- (void)buildRequest:(NSDictionary *)query + responseSender:(RCTResponseSenderBlock)responseSender +{ + NSURL *URL = [RCTConvert NSURL:query[@"url"]]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + request.HTTPMethod = [[RCTConvert NSString:query[@"method"]] uppercaseString] ?: @"GET"; + request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]]; + + BOOL incrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]]; + + NSDictionary *data = [RCTConvert NSDictionary:query[@"data"]]; + [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary *result) { + if (error) { + RCTLogError(@"Error processing request body: %@", error); + // Ideally we'd circle back to JS here and notify an error/abort on the request. + return; + } + request.HTTPBody = result[@"body"]; + NSString *contentType = result[@"contentType"]; + if (contentType) { + [request setValue:contentType forHTTPHeaderField:@"content-type"]; + } + [self sendRequest:request + incrementalUpdates:incrementalUpdates + responseSender:responseSender]; + }]; +} + +- (id)handlerForRequest:(NSURLRequest *)request +{ + NSMutableArray *handlers = [NSMutableArray array]; + for (id module in _bridge.modules.allValues) { + if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) { + if ([(id)module canHandleRequest:request]) { + [handlers addObject:module]; + } + } + } + [handlers sortUsingComparator:^NSComparisonResult(id a, id b) { + float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0; + float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0; + if (priorityA < priorityB) { + return NSOrderedAscending; + } else if (priorityA > priorityB) { + return NSOrderedDescending; + } else { + RCTLogError(@"The RCTURLRequestHandlers %@ and %@ both reported that" + " they can handle the request %@, and have equal priority" + " (%g). This could result in non-deterministic behavior.", + a, b, request, priorityA); + + return NSOrderedSame; + } + }]; + id handler = [handlers lastObject]; + if (!handler) { + RCTLogError(@"No suitable request handler found for %@", request); + } + return handler; +} + +/** + * Process the 'data' part of an HTTP query. + * + * 'data' can be a JSON value of the following forms: + * + * - {"string": "..."}: a simple JS string that will be UTF-8 encoded and sent as the body + * + * - {"uri": "some-uri://..."}: reference to a system resource, e.g. an image in the asset library + * + * - {"formData": [...]}: list of data payloads that will be combined into a multipart/form-data request + * + * If successful, the callback be called with a result dictionary containing the following (optional) keys: + * + * - @"body" (NSData): the body of the request + * + * - @"contentType" (NSString): the content type header of the request + * + */ +- (void)processDataForHTTPQuery:(NSDictionary *)query callback:(nonnull void (^)(NSError *error, NSDictionary *result))callback +{ + if (!query) { + callback(nil, nil); + return; + } + NSData *body = [RCTConvert NSData:query[@"string"]]; + if (body) { + callback(nil, @{@"body": body}); + return; + } + NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]]; + if (request) { + id handler = [self handlerForRequest:request]; + (void)[[RCTDataLoader alloc] initWithRequest:request handler:handler callback:^(NSData *data, NSString *MIMEType, NSError *error) { + if (data) { + callback(nil, @{@"body": data, @"contentType": MIMEType}); + } else { + callback(error, nil); + } + }]; + return; + } + NSDictionaryArray *formData = [RCTConvert NSDictionaryArray:query[@"formData"]]; + if (formData != nil) { + RCTHTTPFormDataHelper *formDataHelper = [[RCTHTTPFormDataHelper alloc] init]; + formDataHelper.dataManager = self; + [formDataHelper process:formData callback:callback]; + return; + } + // Nothing in the data payload, at least nothing we could understand anyway. + // Ignore and treat it as if it were null. + callback(nil, nil); +} + +- (void)sendRequest:(NSURLRequest *)request + incrementalUpdates:(BOOL)incrementalUpdates + responseSender:(RCTResponseSenderBlock)responseSender +{ + id handler = [self handlerForRequest:request]; + id token = [handler sendRequest:request withDelegate:self]; + if (token) { + RCTActiveURLRequest *activeRequest = [[RCTActiveURLRequest alloc] init]; + activeRequest.requestID = @(++_currentRequestID); + activeRequest.request = request; + activeRequest.handler = handler; + activeRequest.incrementalUpdates = incrementalUpdates; + [_activeRequests setObject:activeRequest forKey:token]; + responseSender(@[activeRequest.requestID]); + } +} + +- (void)sendData:(NSData *)data forRequestToken:(id)requestToken +{ + if (data.length == 0) { + return; + } + + RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken]; + + // Get text encoding + NSURLResponse *response = request.response; + NSStringEncoding encoding = NSUTF8StringEncoding; + if (response.textEncodingName) { + CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); + encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); + } + + NSString *responseText = [[NSString alloc] initWithData:data encoding:encoding]; + if (!responseText && data.length) { + RCTLogError(@"Received data was invalid."); + return; + } + + NSArray *responseJSON = @[request.requestID, responseText ?: @""]; + [_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkData" + body:responseJSON]; +} + +#pragma mark - RCTURLRequestDelegate + +- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response +{ + RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken]; + RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken); + + request.response = response; + + NSHTTPURLResponse *httpResponse = nil; + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + // Might be a local file request + httpResponse = (NSHTTPURLResponse *)response; + } + + NSArray *responseJSON = @[request.requestID, + @(httpResponse.statusCode ?: 200), + httpResponse.allHeaderFields ?: @{}, + ]; + + [_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse" + body:responseJSON]; +} + +- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data +{ + RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken]; + RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken); + + if (request.incrementalUpdates) { + [self sendData:data forRequestToken:requestToken]; + } else { + [request.data appendData:data]; + } +} + +- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error +{ + RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken]; + RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken); + + if (!request.incrementalUpdates) { + [self sendData:request.data forRequestToken:requestToken]; + } + + NSArray *responseJSON = @[request.requestID, + error.localizedDescription ?: [NSNull null] + ]; + + [_bridge.eventDispatcher sendDeviceEventWithName:@"didCompleteNetworkResponse" + body:responseJSON]; + + [_activeRequests removeObjectForKey:requestToken]; +} + +#pragma mark - JS API + +RCT_EXPORT_METHOD(sendRequest:(NSDictionary *)query + responseSender:(RCTResponseSenderBlock)responseSender) +{ + [self buildRequest:query responseSender:responseSender]; +} + +RCT_EXPORT_METHOD(cancelRequest:(NSNumber *)requestID) +{ + id requestToken = nil; + RCTActiveURLRequest *activeRequest = nil; + for (id token in _activeRequests) { + RCTActiveURLRequest *request = [_activeRequests objectForKey:token]; + if ([request.requestID isEqualToNumber:requestID]) { + activeRequest = request; + requestToken = token; + break; + } + } + + id handler = activeRequest.handler; + if ([handler respondsToSelector:@selector(cancelRequest:)]) { + [activeRequest.handler cancelRequest:requestToken]; } } diff --git a/IntegrationTests/main.m b/Libraries/Network/RCTHTTPRequestHandler.h similarity index 60% rename from IntegrationTests/main.m rename to Libraries/Network/RCTHTTPRequestHandler.h index fe6bea2f8..b8a7a3e26 100644 --- a/IntegrationTests/main.m +++ b/Libraries/Network/RCTHTTPRequestHandler.h @@ -7,11 +7,9 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import -#import "AppDelegate.h" +#import "RCTURLRequestHandler.h" +#import "RCTInvalidating.h" -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} +@interface RCTHTTPRequestHandler : NSObject + +@end diff --git a/Libraries/Network/RCTHTTPRequestHandler.m b/Libraries/Network/RCTHTTPRequestHandler.m new file mode 100644 index 000000000..7c1a5ac68 --- /dev/null +++ b/Libraries/Network/RCTHTTPRequestHandler.m @@ -0,0 +1,107 @@ +/** + * 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 "RCTHTTPRequestHandler.h" + +#import "RCTAssert.h" +#import "RCTConvert.h" +#import "RCTEventDispatcher.h" +#import "RCTImageLoader.h" +#import "RCTLog.h" +#import "RCTUtils.h" + +@interface RCTHTTPRequestHandler () + +@end + +@implementation RCTHTTPRequestHandler +{ + NSMapTable *_delegates; + NSURLSession *_session; +} + +RCT_EXPORT_MODULE() + +- (instancetype)init +{ + if ((self = [super init])) { + _delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory + valueOptions:NSPointerFunctionsStrongMemory + capacity:0]; + } + return self; +} + +- (void)invalidate +{ + [_session invalidateAndCancel]; + _session = nil; + _delegates = nil; +} + +- (BOOL)isValid +{ + return _delegates != nil; +} + +#pragma mark - NSURLRequestHandler + +- (BOOL)canHandleRequest:(NSURLRequest *)request +{ + return [@[@"http", @"https", @"file"] containsObject:[request.URL.scheme lowercaseString]]; +} + +- (id)sendRequest:(NSURLRequest *)request + withDelegate:(id)delegate +{ + // Lazy setup + if (!_session && [self isValid]) { + NSOperationQueue *callbackQueue = [[NSOperationQueue alloc] init]; + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + _session = [NSURLSession sessionWithConfiguration:configuration + delegate:self + delegateQueue:callbackQueue]; + } + + NSURLSessionDataTask *task = [_session dataTaskWithRequest:request]; + [_delegates setObject:delegate forKey:task]; + [task resume]; + return task; +} + +- (void)cancelRequest:(NSURLSessionDataTask *)requestToken +{ + [requestToken cancel]; +} + +#pragma mark - NSURLSession delegate + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)task +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler +{ + [[_delegates objectForKey:task] URLRequest:task didReceiveResponse:response]; + completionHandler(NSURLSessionResponseAllow); +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)task + didReceiveData:(NSData *)data +{ + [[_delegates objectForKey:task] URLRequest:task didReceiveData:data]; +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error +{ + [[_delegates objectForKey:task] URLRequest:task didCompleteWithError:error]; + [_delegates removeObjectForKey:task]; +} + +@end diff --git a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj index 1dca7fe6d..dba7c65fa 100644 --- a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj +++ b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTReachability.m */; }; + 352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; }; 58B512081A9E6CE300147676 /* RCTDataManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTDataManager.m */; }; /* End PBXBuildFile section */ @@ -26,6 +27,8 @@ /* Begin PBXFileReference section */ 1372B7351AB03E7B00659ED6 /* RCTReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTReachability.h; sourceTree = ""; }; 1372B7361AB03E7B00659ED6 /* RCTReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTReachability.m; sourceTree = ""; }; + 352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = ""; }; + 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTHTTPRequestHandler.m; sourceTree = ""; }; 58B511DB1A9E6C8500147676 /* libRCTNetwork.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTNetwork.a; sourceTree = BUILT_PRODUCTS_DIR; }; 58B512061A9E6CE300147676 /* RCTDataManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDataManager.h; sourceTree = ""; }; 58B512071A9E6CE300147676 /* RCTDataManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDataManager.m; sourceTree = ""; }; @@ -47,6 +50,8 @@ children = ( 58B512061A9E6CE300147676 /* RCTDataManager.h */, 58B512071A9E6CE300147676 /* RCTDataManager.m */, + 352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */, + 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */, 1372B7351AB03E7B00659ED6 /* RCTReachability.h */, 1372B7361AB03E7B00659ED6 /* RCTReachability.m */, 58B511DC1A9E6C8500147676 /* Products */, @@ -121,6 +126,7 @@ files = ( 1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */, 58B512081A9E6CE300147676 /* RCTDataManager.m in Sources */, + 352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -209,6 +215,7 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../React/**", + "$(SRCROOT)/../Image/**", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; @@ -226,6 +233,7 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../React/**", + "$(SRCROOT)/../Image/**", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; diff --git a/Libraries/Network/XMLHttpRequest.ios.js b/Libraries/Network/XMLHttpRequest.ios.js index 9249047da..151781c91 100644 --- a/Libraries/Network/XMLHttpRequest.ios.js +++ b/Libraries/Network/XMLHttpRequest.ios.js @@ -11,30 +11,102 @@ */ 'use strict'; +var FormData = require('FormData'); var RCTDataManager = require('NativeModules').DataManager; +var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); var XMLHttpRequestBase = require('XMLHttpRequestBase'); class XMLHttpRequest extends XMLHttpRequestBase { + _requestId: ?number; + _subscriptions: [any]; + + constructor() { + super(); + this._requestId = null; + this._subscriptions = []; + } + + _didCreateRequest(requestId: number): void { + this._requestId = requestId; + this._subscriptions.push(RCTDeviceEventEmitter.addListener( + 'didReceiveNetworkResponse', + (args) => this._didReceiveResponse.call(this, args[0], args[1], args[2]) + )); + this._subscriptions.push(RCTDeviceEventEmitter.addListener( + 'didReceiveNetworkData', + (args) => this._didReceiveData.call(this, args[0], args[1]) + )); + this._subscriptions.push(RCTDeviceEventEmitter.addListener( + 'didCompleteNetworkResponse', + (args) => this._didCompleteResponse.call(this, args[0], args[1]) + )); + } + + _didReceiveResponse(requestId: number, status: number, responseHeaders: ?Object): void { + if (requestId === this._requestId) { + this.status = status; + this.setResponseHeaders(responseHeaders); + this.setReadyState(this.HEADERS_RECEIVED); + } + } + + _didReceiveData(requestId: number, responseText: string): void { + if (requestId === this._requestId) { + if (!this.responseText) { + this.responseText = responseText; + } else { + this.responseText += responseText; + } + this.setReadyState(this.LOADING); + } + } + + _didCompleteResponse(requestId: number, error: string): void { + if (requestId === this._requestId) { + if (error) { + this.responseText = error; + } + this._clearSubscriptions(); + this._requestId = null; + this.setReadyState(this.DONE); + } + } + + _clearSubscriptions(): void { + for (var i = 0; i < this._subscriptions.length; i++) { + var sub = this._subscriptions[i]; + sub.remove(); + } + this._subscriptions = []; + } + sendImpl(method: ?string, url: ?string, headers: Object, data: any): void { - RCTDataManager.queryData( - 'http', + if (typeof data === 'string') { + data = {string: data}; + } + if (data instanceof FormData) { + data = {formData: data.getParts()}; + } + RCTDataManager.sendRequest( { - method: method, - url: url, - data: data, - headers: headers, + method, + url, + data, + headers, + incrementalUpdates: this.onreadystatechange ? true : false, }, - this.callback.bind(this) + this._didCreateRequest.bind(this) ); } abortImpl(): void { - console.warn( - 'XMLHttpRequest: abort() cancels JS callbacks ' + - 'but not native HTTP request.' - ); + if (this._requestId) { + RCTDataManager.cancelRequest(this._requestId); + this._clearSubscriptions(); + this._requestId = null; + } } } diff --git a/Libraries/Network/XMLHttpRequestBase.js b/Libraries/Network/XMLHttpRequestBase.js index 3570e4bf2..9d06f486a 100644 --- a/Libraries/Network/XMLHttpRequestBase.js +++ b/Libraries/Network/XMLHttpRequestBase.js @@ -1,8 +1,13 @@ /** - * 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. * - * @flow * @providesModule XMLHttpRequestBase + * @flow */ 'use strict'; @@ -30,6 +35,7 @@ class XMLHttpRequestBase { _headers: Object; _sent: boolean; _aborted: boolean; + _lowerCaseResponseHeaders: Object; constructor() { this.UNSENT = 0; @@ -38,46 +44,52 @@ class XMLHttpRequestBase { this.LOADING = 3; this.DONE = 4; - this.onreadystatechange = undefined; - this.upload = undefined; /* Upload not supported */ - this.readyState = this.UNSENT; - this.responseHeaders = undefined; - this.responseText = undefined; - this.status = 0; + this.onreadystatechange = null; + this.onload = null; + this.upload = undefined; /* Upload not supported yet */ + this._reset(); this._method = null; this._url = null; - this._headers = {}; - this._sent = false; this._aborted = false; } + _reset() { + this.readyState = this.UNSENT; + this.responseHeaders = undefined; + this.responseText = ''; + this.status = 0; + + this._headers = {}; + this._sent = false; + this._lowerCaseResponseHeaders = {}; + } + getAllResponseHeaders(): ?string { - if (this.responseHeaders) { - var headers = []; - for (var headerName in this.responseHeaders) { - headers.push(headerName + ': ' + this.responseHeaders[headerName]); - } - return headers.join('\n'); + if (!this.responseHeaders) { + // according to the spec, return null if no response has been received + return null; } - // according to the spec, return null <==> no response has been received - return null; + var headers = this.responseHeaders || {}; + return Object.keys(headers).map((headerName) => { + return headerName + ': ' + headers[headerName]; + }).join('\n'); } getResponseHeader(header: string): ?string { - if (this.responseHeaders) { - var value = this.responseHeaders[header.toLowerCase()]; - return value !== undefined ? value : null; - } - return null; + var value = this._lowerCaseResponseHeaders[header.toLowerCase()]; + return value !== undefined ? value : null; } setRequestHeader(header: string, value: any): void { + if (this.readyState !== this.OPENED) { + throw new Error('Request has not been opened'); + } this._headers[header.toLowerCase()] = value; } open(method: string, url: string, async: ?boolean): void { - /* Other optional arguments are not supported */ + /* Other optional arguments are not supported yet */ if (this.readyState !== this.UNSENT) { throw new Error('Cannot open, already sending'); } @@ -85,10 +97,11 @@ class XMLHttpRequestBase { // async is default throw new Error('Synchronous http requests are not supported'); } + this._reset(); this._method = method; this._url = url; this._aborted = false; - this._setReadyState(this.OPENED); + this.setReadyState(this.OPENED); } sendImpl(method: ?string, url: ?string, headers: Object, data: any): void { @@ -111,20 +124,18 @@ class XMLHttpRequestBase { } abort(): void { + this._aborted = true; this.abortImpl(); // only call onreadystatechange if there is something to abort, // below logic is per spec if (!(this.readyState === this.UNSENT || (this.readyState === this.OPENED && !this._sent) || this.readyState === this.DONE)) { - this._sent = false; - this._setReadyState(this.DONE); + this._reset(); + this.setReadyState(this.DONE); } - if (this.readyState === this.DONE) { - this._sendLoad(); - } - this.readyState = this.UNSENT; - this._aborted = true; + // Reset again after, in case modified in handler + this._reset(); } callback(status: number, responseHeaders: ?Object, responseText: string): void { @@ -132,18 +143,22 @@ class XMLHttpRequestBase { return; } this.status = status; - // Headers should be case-insensitive - var lcResponseHeaders = {}; - for (var header in responseHeaders) { - lcResponseHeaders[header.toLowerCase()] = responseHeaders[header]; - } - this.responseHeaders = lcResponseHeaders; + this.setResponseHeaders(responseHeaders); this.responseText = responseText; - this._setReadyState(this.DONE); - this._sendLoad(); + this.setReadyState(this.DONE); } - _setReadyState(newState: number): void { + setResponseHeaders(responseHeaders: ?Object): void { + this.responseHeaders = responseHeaders || null; + var headers = responseHeaders || {}; + this._lowerCaseResponseHeaders = + Object.keys(headers).reduce((lcaseHeaders, headerName) => { + lcaseHeaders[headerName.toLowerCase()] = headers[headerName]; + return headers; + }, {}); + } + + setReadyState(newState: number): void { this.readyState = newState; // TODO: workaround flow bug with nullable function checks var onreadystatechange = this.onreadystatechange; @@ -152,6 +167,9 @@ class XMLHttpRequestBase { // event anywhere, let's leave it empty onreadystatechange(null); } + if (newState === this.DONE && !this._aborted) { + this._sendLoad(); + } } _sendLoad(): void { diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 1966c6045..12d6ba456 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -121,7 +121,7 @@ RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions) { - UIUserNotificationType types = UIRemoteNotificationTypeNone; + UIUserNotificationType types = UIUserNotificationTypeNone; if (permissions) { if ([permissions[@"alert"] boolValue]) { types |= UIUserNotificationTypeAlert; diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index 2508b88fe..f7d504b06 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -69,4 +69,21 @@ RCT_EXPORT_METHOD(sendAppEvent:(NSString *)name body:(id)body) [_bridge.eventDispatcher sendAppEventWithName:name body:body]; } +RCT_REMAP_METHOD(shouldResolve, shouldResolve_resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +{ + resolve(@1); +} + +RCT_REMAP_METHOD(shouldReject, shouldReject_resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +{ + reject(nil); +} + +RCT_EXPORT_METHOD(finish:(BOOL)success) +{ + RCTAssert(success, @"RCTTestModule finished without success"); + [self markTestCompleted]; +} + + @end diff --git a/Libraries/ReactNative/createReactNativeComponentClass.js b/Libraries/ReactNative/createReactNativeComponentClass.js index 65af58d5e..3940df012 100644 --- a/Libraries/ReactNative/createReactNativeComponentClass.js +++ b/Libraries/ReactNative/createReactNativeComponentClass.js @@ -35,6 +35,7 @@ var createReactNativeComponentClass = function( this.previousFlattenedStyle = null; }; Constructor.displayName = viewConfig.uiViewClassName; + Constructor.viewConfig = viewConfig; Constructor.prototype = new ReactNativeBaseComponent(viewConfig); Constructor.prototype.constructor = Constructor; diff --git a/Libraries/StyleSheet/flattenStyle.js b/Libraries/StyleSheet/flattenStyle.js index a18bce988..50e061ede 100644 --- a/Libraries/StyleSheet/flattenStyle.js +++ b/Libraries/StyleSheet/flattenStyle.js @@ -13,7 +13,6 @@ var StyleSheetRegistry = require('StyleSheetRegistry'); var invariant = require('invariant'); -var mergeIntoFast = require('mergeIntoFast'); type Atom = number | bool | Object | Array type StyleObj = Atom | Array @@ -25,10 +24,7 @@ function getStyle(style) { return style; } -// TODO: Flow 0.7.0 doesn't refine bools properly so we have to use `any` to -// tell it that this can't be a bool anymore. Should be fixed in 0.8.0, -// after which this can take a ?StyleObj. -function flattenStyle(style: any): ?Object { +function flattenStyle(style: ?StyleObj): ?Object { if (!style) { return undefined; } @@ -42,7 +38,13 @@ function flattenStyle(style: any): ?Object { for (var i = 0; i < style.length; ++i) { var computedStyle = flattenStyle(style[i]); if (computedStyle) { - mergeIntoFast(result, computedStyle); + for (var key in computedStyle) { + result[key] = computedStyle[key]; + + if (__DEV__) { + var value = computedStyle[key]; + } + } } } return result; diff --git a/Libraries/StyleSheet/precomputeStyle.js b/Libraries/StyleSheet/precomputeStyle.js index 1e9a290f8..3294494fa 100644 --- a/Libraries/StyleSheet/precomputeStyle.js +++ b/Libraries/StyleSheet/precomputeStyle.js @@ -127,7 +127,7 @@ function _convertToRadians(value: string): number { function _validateTransform(key, value, transformation) { invariant( !value.getValue, - 'You passed an animated value or spring to a normal component. ' + + 'You passed an Animated.Value to a normal component. ' + 'You need to wrap that component in an Animated. For example, ' + 'replace by .' ); diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index a09bd4f4a..8a56aa714 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -514,10 +514,10 @@ var MessageQueueMixin = { ); // Store callback _before_ sending the request, just in case the MailBox // returns the response in a blocking manner. - if (onSucc) { + if (onFail || onSucc) { this._storeCallbacksInCurrentThread(onFail, onSucc, scope, this._POOLED_CBIDS); onFail && params.push(this._POOLED_CBIDS.errorCallbackID); - params.push(this._POOLED_CBIDS.successCallbackID); + onSucc && params.push(this._POOLED_CBIDS.successCallbackID); } var moduleID = this._remoteModuleNameToModuleID[moduleName]; if (moduleID === undefined || moduleID === null) { diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index 97095a5c9..a8cf9ae5e 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -31,8 +31,11 @@ typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply); RCTSparseArray *_callbacks; dispatch_semaphore_t _socketOpenSemaphore; NSMutableDictionary *_injectedObjects; + NSURL *_url; } +RCT_EXPORT_MODULE() + - (instancetype)init { return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081/debugger-proxy"]]; @@ -41,41 +44,45 @@ typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply); - (instancetype)initWithURL:(NSURL *)URL { if (self = [super init]) { - - _jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL); - _socket = [[RCTSRWebSocket alloc] initWithURL:URL]; - _socket.delegate = self; - _callbacks = [[RCTSparseArray alloc] init]; - _injectedObjects = [[NSMutableDictionary alloc] init]; - [_socket setDelegateDispatchQueue:_jsQueue]; - - NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:URL]; - [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil]; - - if (![self connectToProxy]) { - RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If \ - you are running on the device, check if you have the right IP \ - address in `RCTWebSocketExecutor.m`.", URL); - [self invalidate]; - return nil; - } - - NSInteger retries = 3; - BOOL runtimeIsReady = [self prepareJSRuntime]; - while (!runtimeIsReady && retries > 0) { - runtimeIsReady = [self prepareJSRuntime]; - retries--; - } - if (!runtimeIsReady) { - RCTLogError(@"Runtime is not ready. Make sure Chrome is running and not " - "paused on a breakpoint or exception and try reloading again."); - [self invalidate]; - return nil; - } + _url = URL; } return self; } +- (void)setUp +{ + _jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL); + _socket = [[RCTSRWebSocket alloc] initWithURL:_url]; + _socket.delegate = self; + _callbacks = [[RCTSparseArray alloc] init]; + _injectedObjects = [[NSMutableDictionary alloc] init]; + [_socket setDelegateDispatchQueue:_jsQueue]; + + NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:_url]; + [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil]; + + if (![self connectToProxy]) { + RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If \ + you are running on the device, check if you have the right IP \ + address in `RCTWebSocketExecutor.m`.", _url); + [self invalidate]; + return; + } + + NSInteger retries = 3; + BOOL runtimeIsReady = [self prepareJSRuntime]; + while (!runtimeIsReady && retries > 0) { + runtimeIsReady = [self prepareJSRuntime]; + retries--; + } + if (!runtimeIsReady) { + RCTLogError(@"Runtime is not ready. Make sure Chrome is running and not " + "paused on a breakpoint or exception and try reloading again."); + [self invalidate]; + return; + } +} + - (BOOL)connectToProxy { _socketOpenSemaphore = dispatch_semaphore_create(0); diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 6b1c992cc..3372192ea 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -62,6 +62,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { DeviceEventEmitter: require('RCTDeviceEventEmitter'), NativeAppEventEmitter: require('RCTNativeAppEventEmitter'), NativeModules: require('NativeModules'), + Platform: require('Platform'), requireNativeComponent: require('requireNativeComponent'), addons: { diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 98808fb3f..841d7c0b1 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -47,12 +47,17 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { RCTBridgeFieldFlushDateMillis }; +typedef NS_ENUM(NSUInteger, RCTJavaScriptFunctionKind) { + RCTJavaScriptFunctionKindNormal, + RCTJavaScriptFunctionKindAsync, +}; + #ifdef __LP64__ -typedef uint64_t RCTHeaderValue; +typedef struct mach_header_64 *RCTHeaderValue; typedef struct section_64 RCTHeaderSection; #define RCTGetSectByNameFromHeader getsectbynamefromheader_64 #else -typedef uint32_t RCTHeaderValue; +typedef struct mach_header *RCTHeaderValue; typedef struct section RCTHeaderSection; #define RCTGetSectByNameFromHeader getsectbynamefromheader #endif @@ -98,15 +103,15 @@ static NSArray *RCTJSMethods(void) dladdr(&RCTJSMethods, &info); const RCTHeaderValue mach_header = (RCTHeaderValue)info.dli_fbase; - const RCTHeaderSection *section = RCTGetSectByNameFromHeader((void *)mach_header, "__DATA", "RCTImport"); - - if (section) { - for (RCTHeaderValue addr = section->offset; - addr < section->offset + section->size; + unsigned long size = 0; + const uint8_t *sectionData = getsectiondata(mach_header, "__DATA", "RCTImport", &size); + if (sectionData) { + for (const uint8_t *addr = sectionData; + addr < sectionData + size; addr += sizeof(const char **)) { // Get data entry - NSString *entry = @(*(const char **)(mach_header + addr)); + NSString *entry = @(*(const char **)addr); [uniqueMethods addObject:entry]; } } @@ -139,15 +144,15 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) dladdr(&RCTBridgeModuleClassesByModuleID, &info); const RCTHeaderValue mach_header = (RCTHeaderValue)info.dli_fbase; - const RCTHeaderSection *section = RCTGetSectByNameFromHeader((void *)mach_header, "__DATA", "RCTExportModule"); - - if (section) { - for (RCTHeaderValue addr = section->offset; - addr < section->offset + section->size; + unsigned long size; + const uint8_t *sectionData = getsectiondata(mach_header, "__DATA", "RCTExportModule", &size); + if (sectionData) { + for (const uint8_t *addr = sectionData; + addr < sectionData + size; addr += sizeof(const char **)) { // Get data entry - NSString *entry = @(*(const char **)(mach_header + addr)); + NSString *entry = @(*(const char **)addr); NSArray *parts = [[entry substringWithRange:(NSRange){2, entry.length - 3}] componentsSeparatedByString:@" "]; @@ -204,6 +209,27 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) return RCTModuleClassesByID; } +// TODO: Can we just replace RCTMakeError with this function instead? +static NSDictionary *RCTJSErrorFromNSError(NSError *error) +{ + NSString *errorMessage; + NSArray *stackTrace = [NSThread callStackSymbols]; + NSMutableDictionary *errorInfo = + [NSMutableDictionary dictionaryWithObject:stackTrace forKey:@"nativeStackIOS"]; + + if (error) { + errorMessage = error.localizedDescription ?: @"Unknown error from a native module"; + errorInfo[@"domain"] = error.domain ?: RCTErrorDomain; + errorInfo[@"code"] = @(error.code); + } else { + errorMessage = @"Unknown error from a native module"; + errorInfo[@"domain"] = RCTErrorDomain; + errorInfo[@"code"] = @-1; + } + + return RCTMakeError(errorMessage, nil, errorInfo); +} + @class RCTBatchedBridge; @interface RCTBridge () @@ -239,6 +265,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) @property (nonatomic, copy, readonly) NSString *moduleClassName; @property (nonatomic, copy, readonly) NSString *JSMethodName; @property (nonatomic, assign, readonly) SEL selector; +@property (nonatomic, assign, readonly) RCTJavaScriptFunctionKind functionKind; @end @@ -390,22 +417,64 @@ case _value: { \ [invocation setArgument:returnValue atIndex:index]; - free(returnValue); - }]; - break; + free(returnValue); + }]; + break; + } + + default: + defaultCase(argumentType); + } + } else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) { + addBlockArgument(); + } else if ([argumentName isEqualToString:@"RCTPromiseResolveBlock"]) { + RCTAssert(i == numberOfArguments - 2, + @"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]", + _moduleClassName, objCMethodName); + RCT_ARG_BLOCK( + if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { + RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise resolver ID", index, + json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); + return; } - default: - defaultCase(argumentType); - } - } else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) { - addBlockArgument(); - } else { + // Marked as autoreleasing, because NSInvocation doesn't retain arguments + __autoreleasing RCTPromiseResolveBlock value = (^(id result) { + NSArray *arguments = result ? @[result] : @[]; + [bridge _invokeAndProcessModule:@"BatchedBridge" + method:@"invokeCallbackAndReturnFlushedQueue" + arguments:@[json, arguments] + context:context]; + }); + ) + _functionKind = RCTJavaScriptFunctionKindAsync; + } else if ([argumentName isEqualToString:@"RCTPromiseRejectBlock"]) { + RCTAssert(i == numberOfArguments - 1, + @"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]", + _moduleClassName, objCMethodName); + RCT_ARG_BLOCK( + if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { + RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise rejecter ID", index, + json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); + return; + } - // Unknown argument type - RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert" - " to support this type.", argumentName, [self methodName]); - } + // Marked as autoreleasing, because NSInvocation doesn't retain arguments + __autoreleasing RCTPromiseRejectBlock value = (^(NSError *error) { + NSDictionary *errorJSON = RCTJSErrorFromNSError(error); + [bridge _invokeAndProcessModule:@"BatchedBridge" + method:@"invokeCallbackAndReturnFlushedQueue" + arguments:@[json, @[errorJSON]] + context:context]; + }); + ) + _functionKind = RCTJavaScriptFunctionKindAsync; + } else { + + // Unknown argument type + RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert" + " to support this type.", argumentName, [self methodName]); + } } _argumentBlocks = [argumentBlocks copy]; @@ -427,9 +496,18 @@ case _value: { \ // Safety check if (arguments.count != _argumentBlocks.count) { + NSInteger actualCount = arguments.count; + NSInteger expectedCount = _argumentBlocks.count; + + // Subtract the implicit Promise resolver and rejecter functions for implementations of async functions + if (_functionKind == RCTJavaScriptFunctionKindAsync) { + actualCount -= 2; + expectedCount -= 2; + } + RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd", RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, - arguments.count, _argumentBlocks.count); + actualCount, expectedCount); return; } } @@ -481,21 +559,23 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void) dladdr(&RCTExportedMethodsByModuleID, &info); const RCTHeaderValue mach_header = (RCTHeaderValue)info.dli_fbase; - const RCTHeaderSection *section = RCTGetSectByNameFromHeader((void *)mach_header, "__DATA", "RCTExport"); - if (section == NULL) { + unsigned long size; + const uint8_t *sectionData = getsectiondata(mach_header, "__DATA", "RCTExport", &size); + + if (sectionData == NULL) { return; } NSArray *classes = RCTBridgeModuleClassesByModuleID(); NSMutableDictionary *methodsByModuleClassName = [NSMutableDictionary dictionaryWithCapacity:[classes count]]; - for (RCTHeaderValue addr = section->offset; - addr < section->offset + section->size; + for (const uint8_t *addr = sectionData; + addr < sectionData + size; addr += sizeof(const char **) * 3) { // Get data entry - const char **entries = (const char **)(mach_header + addr); + const char **entries = (const char **) addr; // Create method RCTModuleMethod *moduleMethod = @@ -533,7 +613,7 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void) * }, * "methodName2": { * "methodID": 1, - * "type": "remote" + * "type": "remoteAsync" * }, * etc... * }, @@ -557,7 +637,7 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName) [methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) { methodsByName[method.JSMethodName] = @{ @"methodID": @(methodID), - @"type": @"remote", + @"type": method.functionKind == RCTJavaScriptFunctionKindAsync ? @"remoteAsync" : @"remote", }; }]; @@ -819,7 +899,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin @implementation RCTBatchedBridge { BOOL _loading; - id _javaScriptExecutor; + __weak id _javaScriptExecutor; RCTSparseArray *_modulesByID; RCTSparseArray *_queuesByID; dispatch_queue_t _methodQueue; @@ -856,13 +936,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin [_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } - /** - * Initialize executor to allow enqueueing calls - */ - Class executorClass = self.executorClass ?: [RCTContextExecutor class]; - _javaScriptExecutor = RCTCreateExecutor(executorClass); - _latestJSExecutor = _javaScriptExecutor; - /** * Initialize and register bridge modules *before* adding the display link * so we don't have threading issues @@ -900,7 +973,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin - (Class)executorClass { - return _parentBridge.executorClass; + return _parentBridge.executorClass ?: [RCTContextExecutor class]; } - (void)setExecutorClass:(Class)executorClass @@ -965,6 +1038,16 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin // Store modules _modulesByName = [modulesByName copy]; + /** + * The executor is a bridge module, wait for it to be created and set it before + * any other module has access to the bridge + */ + _javaScriptExecutor = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)]; + _latestJSExecutor = _javaScriptExecutor; + RCTSetExecutorID(_javaScriptExecutor); + + [_javaScriptExecutor setUp]; + // Set bridge for (id module in _modulesByName.allValues) { if ([module respondsToSelector:@selector(setBridge:)]) { @@ -1081,6 +1164,10 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin - (NSDictionary *)modules { + if (!self.isValid) { + return nil; + } + RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. " "You may be trying to access a module too early in the startup procedure."); @@ -1111,7 +1198,9 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin // Invalidate modules for (id target in _modulesByID.allObjects) { if ([target respondsToSelector:@selector(invalidate)]) { - [(id)target invalidate]; + [self dispatchBlock:^{ + [(id)target invalidate]; + } forModule:target]; } } @@ -1373,15 +1462,42 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin return; } - // TODO: if we sort the requests by module, we could dispatch once per - // module instead of per request, which would reduce the call overhead. + NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory capacity:_queuesByID.count]; for (NSUInteger i = 0; i < numRequests; i++) { - @autoreleasepool { - [self _handleRequestNumber:i - moduleID:[moduleIDs[i] integerValue] - methodID:[methodIDs[i] integerValue] - params:paramsArrays[i] - context:context]; + id queue = RCTNullIfNil(_queuesByID[moduleIDs[i]]); + NSMutableOrderedSet *set = [buckets objectForKey:queue]; + if (!set) { + set = [[NSMutableOrderedSet alloc] init]; + [buckets setObject:set forKey:queue]; + } + [set addObject:@(i)]; + } + + for (id queue in buckets) { + RCTProfileBeginFlowEvent(); + dispatch_block_t block = ^{ + RCTProfileEndFlowEvent(); + RCTProfileBeginEvent(); + + NSOrderedSet *calls = [buckets objectForKey:queue]; + @autoreleasepool { + for (NSNumber *indexObj in calls) { + NSUInteger index = indexObj.unsignedIntegerValue; + [self _handleRequestNumber:index + moduleID:[moduleIDs[index] integerValue] + methodID:[methodIDs[index] integerValue] + params:paramsArrays[index] + context:context]; + } + } + + RCTProfileEndEvent(RCTCurrentThreadName(), @"objc_call,dispatch_async", @{ @"calls": @(calls.count) }); + }; + + if (queue == (id)kCFNull) { + [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; + } else { + dispatch_async(queue, block); } } @@ -1401,8 +1517,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin params:(NSArray *)params context:(NSNumber *)context { - RCTAssertJSThread(); - if (!self.isValid) { return NO; } @@ -1420,6 +1534,8 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin return NO; } + RCTProfileBeginEvent(); + RCTModuleMethod *method = methods[methodID]; // Look up module @@ -1429,36 +1545,22 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin return NO; } - RCTProfileBeginFlowEvent(); - __weak RCTBatchedBridge *weakSelf = self; - [self dispatchBlock:^{ - RCTProfileEndFlowEvent(); - RCTProfileBeginEvent(); - RCTBatchedBridge *strongSelf = weakSelf; - - if (!strongSelf.isValid) { - // strongSelf has been invalidated since the dispatch_async call and this - // invocation should not continue. - return; + @try { + [method invokeWithBridge:self module:module arguments:params context:context]; + } + @catch (NSException *exception) { + RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception); + if (!RCT_DEBUG && [exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) { + @throw exception; } + } - @try { - [method invokeWithBridge:strongSelf module:module arguments:params context:context]; - } - @catch (NSException *exception) { - RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception); - if (!RCT_DEBUG && [exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) { - @throw exception; - } - } - - RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{ - @"module": method.moduleClassName, - @"method": method.JSMethodName, - @"selector": NSStringFromSelector(method.selector), - @"args": RCTJSONStringify(params ?: [NSNull null], NULL), - }); - } forModuleID:@(moduleID)]; + RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{ + @"module": method.moduleClassName, + @"method": method.JSMethodName, + @"selector": NSStringFromSelector(method.selector), + @"args": RCTJSONStringify(params ?: [NSNull null], NULL), + }); return YES; } diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index f3a9a5a3e..d64e81693 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -17,6 +17,20 @@ */ typedef void (^RCTResponseSenderBlock)(NSArray *response); +/** + * Block that bridge modules use to resolve the JS promise waiting for a result. + * Nil results are supported and are converted to JS's undefined value. + */ +typedef void (^RCTPromiseResolveBlock)(id result); + +/** + * Block that bridge modules use to reject the JS promise waiting for a result. + * The error may be nil but it is preferable to pass an NSError object for more + * precise error messages. + */ +typedef void (^RCTPromiseRejectBlock)(NSError *error); + + /** * This constant can be returned from +methodQueue to force module * methods to be called on the JavaScript thread. This can have serious @@ -37,7 +51,7 @@ extern const dispatch_queue_t RCTJSThread; * A reference to the RCTBridge. Useful for modules that require access * to bridge features, such as sending events or making JS calls. This * will be set automatically by the bridge when it initializes the module. -* To implement this in your module, just add @synthesize bridge = _bridge; + * To implement this in your module, just add @synthesize bridge = _bridge; */ @property (nonatomic, weak) RCTBridge *bridge; @@ -70,6 +84,26 @@ extern const dispatch_queue_t RCTJSThread; * { ... } * * and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`. + * + * ## Promises + * + * Bridge modules can also define methods that are exported to JavaScript as + * methods that return a Promise, and are compatible with JS async functions. + * + * Declare the last two parameters of your native method to be a resolver block + * and a rejecter block. The resolver block must precede the rejecter block. + * + * For example: + * + * RCT_EXPORT_METHOD(doSomethingAsync:(NSString *)aString + * resolver:(RCTPromiseResolveBlock)resolve + * rejecter:(RCTPromiseRejectBlock)reject + * { ... } + * + * Calling `NativeModules.ModuleName.doSomethingAsync(aString)` from + * JavaScript will return a promise that is resolved or rejected when your + * native method implementation calls the respective block. + * */ #define RCT_EXPORT_METHOD(method) \ RCT_REMAP_METHOD(, method) @@ -118,7 +152,7 @@ extern const dispatch_queue_t RCTJSThread; RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername) /** - * Similar to RCT_EXTERN_MODULE but allows setting a custom JavaScript name + * Like RCT_EXTERN_MODULE, but allows setting a custom JavaScript name. */ #define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \ objc_name : objc_supername \ @@ -136,7 +170,7 @@ extern const dispatch_queue_t RCTJSThread; RCT_EXTERN_REMAP_METHOD(, method) /** - * Similar to RCT_EXTERN_REMAP_METHOD but allows setting a custom JavaScript name + * Like RCT_EXTERN_REMAP_METHOD, but allows setting a custom JavaScript name. */ #define RCT_EXTERN_REMAP_METHOD(js_name, method) \ - (void)__rct_export__##method { \ diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 3bdf59753..0a08326c7 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -118,7 +118,8 @@ RCT_CONVERTER(NSString *, NSString, description) + (NSURLRequest *)NSURLRequest:(id)json { - return [NSURLRequest requestWithURL:[self NSURL:json]]; + NSURL *URL = [self NSURL:json]; + return URL ? [NSURLRequest requestWithURL:URL] : nil; } + (NSDate *)NSDate:(id)json diff --git a/React/Base/RCTDefines.h b/React/Base/RCTDefines.h index 0d985db8a..83a60d61a 100644 --- a/React/Base/RCTDefines.h +++ b/React/Base/RCTDefines.h @@ -18,6 +18,21 @@ #define RCT_EXTERN extern __attribute__((visibility("default"))) #endif +/** + * Nullability for Xcode 6.2 + */ +#if !__has_feature(nullability) +#define NS_ASSUME_NONNULL_BEGIN +#define NS_ASSUME_NONNULL_END +#define nullable +#define nonnull +#define null_unspecified +#define null_resettable +#define __nullable +#define __nonnull +#define __null_unspecified +#endif + /** * The RCT_DEBUG macro can be used to exclude error checking and logging code * from release builds to improve performance and reduce binary size. diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m index 2009df542..b5b2857f0 100644 --- a/React/Base/RCTEventDispatcher.m +++ b/React/Base/RCTEventDispatcher.m @@ -72,6 +72,7 @@ static NSNumber *RCTGetEventID(id event) } @synthesize bridge = _bridge; +@synthesize paused = _paused; RCT_EXPORT_MODULE() @@ -146,6 +147,7 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent); } _eventQueue[eventID] = event; + _paused = NO; [_eventQueueLock unlock]; } @@ -180,6 +182,7 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent); [_eventQueueLock lock]; eventQueue = _eventQueue; _eventQueue = [[NSMutableDictionary alloc] init]; + _paused = YES; [_eventQueueLock unlock]; for (id event in eventQueue.allValues) { diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index 8f1eb8a98..7a3aa2870 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -11,6 +11,7 @@ #import +#import "RCTBridgeModule.h" #import "RCTInvalidating.h" typedef void (^RCTJavaScriptCompleteBlock)(NSError *error); @@ -20,7 +21,13 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); * Abstracts away a JavaScript execution context - we may be running code in a * web view (for debugging purposes), or may be running code in a `JSContext`. */ -@protocol RCTJavaScriptExecutor +@protocol RCTJavaScriptExecutor + +/** + * Used to set up the executor after the bridge has been fully initialized. + * Do any expensive setup in this method instead of `-init`. + */ +- (void)setUp; /** * Executes given method with arguments on JS thread and calls the given callback @@ -61,14 +68,12 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); @end static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID"; -__used static id RCTCreateExecutor(Class executorClass) +__used static void RCTSetExecutorID(id executor) { static NSUInteger executorID = 0; - id executor = [[executorClass alloc] init]; if (executor) { objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN); } - return executor; } __used static NSNumber *RCTGetExecutorID(id executor) diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index d99bb4fe1..9469d3015 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -171,6 +171,9 @@ void _RCTLogFormat( // Log to red box if (level >= RCTLOG_REDBOX_LEVEL) { + if (level < RCTLOG_FATAL_LEVEL) { + [[RCTRedBox sharedInstance] setNextBackgroundColor:[UIColor colorWithRed:0.9 green:0.4 blue:0.2 alpha:1]]; + } [[RCTRedBox sharedInstance] showErrorMessage:message]; } diff --git a/React/Base/RCTRedBox.h b/React/Base/RCTRedBox.h index 9a3a9b49a..7a1f51f36 100644 --- a/React/Base/RCTRedBox.h +++ b/React/Base/RCTRedBox.h @@ -18,6 +18,8 @@ - (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack; - (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack; +- (void)setNextBackgroundColor:(UIColor *)color; + - (NSString *)currentErrorMessage; - (void)dismiss; diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m index 6330454ab..b293a162d 100644 --- a/React/Base/RCTRedBox.m +++ b/React/Base/RCTRedBox.m @@ -29,14 +29,15 @@ NSArray *_lastStackTrace; UITableViewCell *_cachedMessageCell; + UIColor *_redColor; } - (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { - + _redColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1]; self.windowLevel = UIWindowLevelStatusBar + 5; - self.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1]; + self.backgroundColor = _redColor; self.hidden = YES; UIViewController *rootController = [[UIViewController alloc] init]; @@ -132,6 +133,7 @@ - (void)dismiss { self.hidden = YES; + self.backgroundColor = _redColor; [self resignFirstResponder]; [[[[UIApplication sharedApplication] delegate] window] makeKeyWindow]; } @@ -261,6 +263,7 @@ @implementation RCTRedBox { RCTRedBoxWindow *_window; + UIColor *_nextBackgroundColor; } + (instancetype)sharedInstance @@ -273,6 +276,11 @@ return _sharedInstance; } +- (void)setNextBackgroundColor:(UIColor *)color +{ + _nextBackgroundColor = color; +} + - (void)showErrorMessage:(NSString *)message { [self showErrorMessage:message withStack:nil showIfHidden:YES]; @@ -304,6 +312,10 @@ _window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; } [_window showErrorMessage:message withStack:stack showIfHidden:shouldShow]; + if (_nextBackgroundColor) { + _window.backgroundColor = _nextBackgroundColor; + _nextBackgroundColor = nil; + } }); } @@ -334,6 +346,7 @@ - (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack {} - (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow {} - (NSString *)currentErrorMessage { return nil; } +- (void)setNextBackgroundColor:(UIColor *)color {} - (void)dismiss {} @end diff --git a/React/Base/RCTURLRequestDelegate.h b/React/Base/RCTURLRequestDelegate.h new file mode 100644 index 000000000..3ca5b0e01 --- /dev/null +++ b/React/Base/RCTURLRequestDelegate.h @@ -0,0 +1,36 @@ +/** + * 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 + +/** + * An abstract interface used by request handler modules to send + * data back over the bridge back to JS. + */ +@protocol RCTURLRequestDelegate + +/** + * Call this when you first receives a response from the server. This should + * include response headers, etc. + */ +- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response; + +/** + * Call this when you receive data from the server. This can be called multiple + * times with partial data chunks, or just once with the full data packet. + */ +- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data; + +/** + * Call this when the request is complete and/or if an error is encountered. + * For a successful request, the error parameter should be nil. + */ +- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error; + +@end diff --git a/React/Base/RCTURLRequestHandler.h b/React/Base/RCTURLRequestHandler.h new file mode 100644 index 000000000..d5959e5eb --- /dev/null +++ b/React/Base/RCTURLRequestHandler.h @@ -0,0 +1,54 @@ +/** + * 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 "RCTBridgeModule.h" +#import "RCTURLRequestDelegate.h" + +/** + * Provides the interface needed to register a request handler. Request handlers + * are also bridge modules, so should be registered using RCT_EXPORT_MODULE(). + */ +@protocol RCTURLRequestHandler + +/** + * Indicates whether this handler is capable of processing the specified + * request. Typically the handler would examine the scheme/protocol of the + * request URL (and possibly the HTTP method and/or headers) to determine this. + */ +- (BOOL)canHandleRequest:(NSURLRequest *)request; + +/** + * Send a network request and call the delegate with the response data. The + * method should return a token, which can be anything, including the request + * itself. This will be used later to refer to the request in callbacks. The + * `sendRequest:withDelegate:` method *must* return before calling any of the + * delegate methods, or the delegate won't recognize the token. + */ +- (id)sendRequest:(NSURLRequest *)request + withDelegate:(id)delegate; + +@optional + +/** + * Not all request types can be cancelled, but this method can be implemented + * for ones that can. It should be used to free up any resources on ongoing + * processes associated with the request. + */ +- (void)cancelRequest:(id)requestToken; + +/** + * If more than one RCTURLRequestHandler responds YES to `canHandleRequest:` + * then `handlerPriority` is used to determine which one to use. The handler + * with the highest priority will be selected. Default priority is zero. If + * two or more valid handlers have the same priority, the selection order is + * undefined. + */ +- (float)handlerPriority; + +@end diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 641500b38..5150b5c97 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -57,3 +57,10 @@ RCT_EXTERN BOOL RCTRunningInTestEnvironment(void); // Return YES if image has an alpha component RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image); + +// Create an NSError in the NCTErrorDomain +RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message); + +// Convert nil values to NSNull, and vice-versa +RCT_EXTERN id RCTNullIfNil(id value); +RCT_EXTERN id RCTNilIfNull(id value); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 613b13163..a9522ad8c 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -281,3 +281,20 @@ BOOL RCTImageHasAlpha(CGImageRef image) return YES; } } + +NSError *RCTErrorWithMessage(NSString *message) +{ + NSDictionary *errorInfo = @{NSLocalizedDescriptionKey: message}; + NSError *error = [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo]; + return error; +} + +id RCTNullIfNil(id value) +{ + return value ?: (id)kCFNull; +} + +id RCTNilIfNull(id value) +{ + return value == (id)kCFNull ? nil : value; +} diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 627255c37..d17791c9a 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -68,6 +68,8 @@ NSThread *_javaScriptThread; } +RCT_EXPORT_MODULE() + /** * The one tiny pure native hook that we implement is a native logging hook. * You could even argue that this is not necessary - we could plumb logging @@ -233,37 +235,43 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) JSGlobalContextRef ctx; if (context) { ctx = JSGlobalContextRetain(context); - } else { - JSContextGroupRef group = JSContextGroupCreate(); - ctx = JSGlobalContextCreateInGroup(group, NULL); -#if FB_JSC_HACK - JSContextGroupBindToCurrentThread(group); -#endif - JSContextGroupRelease(group); + strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx]; } - - strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx]; - [strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"]; - [strongSelf _addNativeHook:RCTNoop withName:"noop"]; - -#if RCT_DEV - [strongSelf _addNativeHook:RCTConsoleProfile withName:"consoleProfile"]; - [strongSelf _addNativeHook:RCTConsoleProfileEnd withName:"consoleProfileEnd"]; - - for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(toggleProfilingFlag:) - name:event - object:nil]; - } -#endif - }]; } return self; } +- (void)setUp +{ + __weak RCTContextExecutor *weakSelf = self; + [self executeBlockOnJavaScriptQueue:^{ + RCTContextExecutor *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + if (!strongSelf->_context) { + JSGlobalContextRef ctx = JSGlobalContextCreate(NULL); + JSGlobalContextRetain(ctx); + strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx]; + } + [strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"]; + [strongSelf _addNativeHook:RCTNoop withName:"noop"]; +#if RCT_DEV + [strongSelf _addNativeHook:RCTConsoleProfile withName:"consoleProfile"]; + [strongSelf _addNativeHook:RCTConsoleProfileEnd withName:"consoleProfileEnd"]; + + for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(toggleProfilingFlag:) + name:event + object:nil]; + } +#endif + }]; +} + - (void)toggleProfilingFlag:(NSNotification *)notification { JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx); diff --git a/React/Executors/RCTWebViewExecutor.m b/React/Executors/RCTWebViewExecutor.m index 56323fb99..fb3c6f031 100644 --- a/React/Executors/RCTWebViewExecutor.m +++ b/React/Executors/RCTWebViewExecutor.m @@ -46,16 +46,14 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...) NSRegularExpression *_scriptTagsRegex; } +RCT_EXPORT_MODULE() + @synthesize valid = _valid; - (instancetype)initWithWebView:(UIWebView *)webView { if ((self = [super init])) { - _objectsToInject = [[NSMutableDictionary alloc] init]; - _webView = webView ?: [[UIWebView alloc] init]; - _commentsRegex = [NSRegularExpression regularExpressionWithPattern:@"(^ *?\\/\\/.*?$|\\/\\*\\*[\\s\\S]*?\\*\\/)" options:NSRegularExpressionAnchorsMatchLines error:NULL], - _scriptTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\/?script[^>]*?)>" options:0 error:NULL], - _webView.delegate = self; + _webView = webView; } return self; } @@ -65,6 +63,18 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...) return [self initWithWebView:nil]; } +- (void)setUp +{ + if (!_webView) { + _webView = [[UIWebView alloc] init]; + } + + _objectsToInject = [[NSMutableDictionary alloc] init]; + _commentsRegex = [NSRegularExpression regularExpressionWithPattern:@"(^ *?\\/\\/.*?$|\\/\\*\\*[\\s\\S]*?\\*\\/)" options:NSRegularExpressionAnchorsMatchLines error:NULL], + _scriptTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\/?script[^>]*?)>" options:0 error:NULL], + _webView.delegate = self; +} + - (void)invalidate { _valid = NO; diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index 64a5c85f0..4a9815f31 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -44,7 +44,9 @@ RCT_EXPORT_METHOD(reportSoftException:(NSString *)message [_delegate handleSoftJSExceptionWithMessage:message stack:stack]; return; } - // JS already logs the error via console. + RCTRedBox *box = [RCTRedBox sharedInstance]; + [box setNextBackgroundColor:[UIColor colorWithRed:0.9 green:0.4 blue:0.2 alpha:1]]; + [box showErrorMessage:message withStack:stack]; } RCT_EXPORT_METHOD(reportFatalException:(NSString *)message diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index e21c9d16f..4f92cfb05 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -155,6 +155,10 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers) if ([timersToCall count] > 0) { [_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[timersToCall]]; } + + if (_timers.count == 0) { + [self stopTimers]; + } } /** diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 570bdfef7..178281074 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -342,14 +342,19 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa CGRect frame = rootView.frame; // Register shadow view + __weak RCTUIManager *weakSelf = self; dispatch_async(_shadowQueue, ^{ + RCTUIManager *strongSelf = weakSelf; + if (!strongSelf.isValid) { + return; + } RCTShadowView *shadowView = [[RCTShadowView alloc] init]; shadowView.reactTag = reactTag; shadowView.frame = frame; shadowView.backgroundColor = rootView.backgroundColor; shadowView.viewName = NSStringFromClass([rootView class]); - _shadowViewRegistry[shadowView.reactTag] = shadowView; - [_rootViewTags addObject:reactTag]; + strongSelf->_shadowViewRegistry[shadowView.reactTag] = shadowView; + [strongSelf->_rootViewTags addObject:reactTag]; }); } @@ -379,8 +384,13 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa NSNumber *reactTag = rootView.reactTag; RCTAssert(RCTIsReactRootView(reactTag), @"Specified view %@ is not a root view", reactTag); + __weak RCTUIManager *weakSelf = self; dispatch_async(_shadowQueue, ^{ - RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag]; + RCTUIManager *strongSelf = weakSelf; + if (!strongSelf.isValid) { + return; + } + RCTShadowView *rootShadowView = strongSelf->_shadowViewRegistry[reactTag]; RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag); rootShadowView.backgroundColor = color; [self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootShadowView]; diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 3c0cfe722..513773b6d 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -101,6 +101,8 @@ 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CoreLocation.m"; sourceTree = ""; }; 13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+MapKit.h"; sourceTree = ""; }; 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+MapKit.m"; sourceTree = ""; }; + 1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTURLRequestDelegate.h; sourceTree = ""; }; + 1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTURLRequestHandler.h; sourceTree = ""; }; 134FCB391A6E7F0800051CC8 /* RCTContextExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTContextExecutor.h; sourceTree = ""; }; 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutor.m; sourceTree = ""; }; 134FCB3B1A6E7F0800051CC8 /* RCTWebViewExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewExecutor.h; sourceTree = ""; }; @@ -396,6 +398,8 @@ 83CBBA491A601E3B00E9B192 /* Base */ = { isa = PBXGroup; children = ( + 1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */, + 1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */, 14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */, 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */, 83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */, diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/require.js b/packager/react-packager/src/DependencyResolver/haste/polyfills/require.js index 807058480..04a0bff75 100644 --- a/packager/react-packager/src/DependencyResolver/haste/polyfills/require.js +++ b/packager/react-packager/src/DependencyResolver/haste/polyfills/require.js @@ -117,11 +117,12 @@ } var _now = _performance ? - _performance.now.bind(_performance) : function(){return 0;}; + _performance.now.bind(_performance) : function(){ return 0; }; var _factoryStackCount = 0; var _factoryTime = 0; var _totalFactories = 0; + var _inGuard = false; /** * The require function conforming to CommonJS spec: @@ -188,9 +189,15 @@ } return module.exports; } - - if (global.ErrorUtils && !global.ErrorUtils.inGuard()) { - return ErrorUtils.applyWithGuard(require, this, arguments); + if (global.ErrorUtils && !_inGuard) { + _inGuard = true; + try { + var ret = require.apply(this, arguments); + } catch(e) { + global.ErrorUtils.reportFatalError(e); + } + _inGuard = false; + return ret; } if (!module) {