diff --git a/Examples/UIExplorer/SliderIOSExample.js b/Examples/UIExplorer/SliderIOSExample.js index 531473aea..ae6aca144 100644 --- a/Examples/UIExplorer/SliderIOSExample.js +++ b/Examples/UIExplorer/SliderIOSExample.js @@ -53,6 +53,7 @@ var styles = StyleSheet.create({ }); exports.title = ''; +exports.displayName = 'SliderExample'; exports.description = 'Slider input for numeric values'; exports.examples = [ { diff --git a/Examples/UIExplorer/SwitchExample.js b/Examples/UIExplorer/SwitchExample.js index 1db9001cf..3c296d976 100644 --- a/Examples/UIExplorer/SwitchExample.js +++ b/Examples/UIExplorer/SwitchExample.js @@ -121,6 +121,7 @@ var EventSwitchExample = React.createClass({ }); exports.title = ''; +exports.displayName = 'SwitchExample'; exports.description = 'Native boolean input'; exports.examples = [ { diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js index 7cd582277..e29b07da4 100644 --- a/Examples/UIExplorer/TextExample.ios.js +++ b/Examples/UIExplorer/TextExample.ios.js @@ -59,6 +59,7 @@ var AttributeToggler = React.createClass({ exports.title = ''; exports.description = 'Base component for rendering styled text.'; +exports.displayName = 'TextExample'; exports.examples = [ { title: 'Wrap', diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index ff024216a..0b4496435 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; }; 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; }; + 58005BF21ABA80A60062E044 /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58005BEE1ABA80530062E044 /* libRCTTest.a */; }; D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; }; /* End PBXBuildFile section */ @@ -96,6 +97,13 @@ remoteGlobalIDString = 134814201AA4EA6300B7C361; remoteInfo = RCTPushNotification; }; + 58005BED1ABA80530062E044 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 580C376F1AB104AF0015E709; + remoteInfo = RCTTest; + }; D85B829B1AB6D5CE003F4FE2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */; @@ -126,6 +134,7 @@ 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = UIExplorer/main.m; 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 = ""; }; D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ @@ -143,6 +152,7 @@ files = ( 00D2771C1AB8C55500DC1E48 /* libicucore.dylib in Frameworks */, 00D2771A1AB8C3E100DC1E48 /* libRCTWebSocketDebugger.a in Frameworks */, + 58005BF21ABA80A60062E044 /* libRCTTest.a in Frameworks */, D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */, 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */, 147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */, @@ -186,16 +196,17 @@ 1316A21D1AA397F400C0188E /* Libraries */ = { isa = PBXGroup; children = ( - D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */, + 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */, 14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */, 14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */, - 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */, 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */, 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */, 13417FE31AA91428003F314A /* RCTImage.xcodeproj */, 134180261AA91779003F314A /* RCTNetwork.xcodeproj */, + 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */, 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */, 00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */, + D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */, 00D2771B1AB8C55500DC1E48 /* libicucore.dylib */, ); name = Libraries; @@ -278,6 +289,14 @@ name = Products; sourceTree = ""; }; + 58005BE51ABA80530062E044 /* Products */ = { + isa = PBXGroup; + children = ( + 58005BEE1ABA80530062E044 /* libRCTTest.a */, + ); + name = Products; + sourceTree = ""; + }; 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( @@ -394,6 +413,10 @@ ProductGroup = 14DC67E81AB71876001358AB /* Products */; ProjectRef = 14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */; }, + { + ProductGroup = 58005BE51ABA80530062E044 /* Products */; + ProjectRef = 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */; + }, { ProductGroup = 13417FEB1AA914B8003F314A /* Products */; ProjectRef = 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */; @@ -483,6 +506,13 @@ remoteRef = 14DC67F01AB71876001358AB /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 58005BEE1ABA80530062E044 /* libRCTTest.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTTest.a; + remoteRef = 58005BED1ABA80530062E044 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -562,6 +592,7 @@ ); GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", + "FB_REFERENCE_IMAGE_DIR=\"\\\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\\\"\"", "$(inherited)", ); INFOPLIST_FILE = UIExplorerTests/Info.plist; diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index f24ceafff..73f016cdd 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -12,6 +12,7 @@ var React = require('react-native'); var { + AppRegistry, ListView, PixelRatio, ScrollView, @@ -23,6 +24,8 @@ var { } = React; var ReactNavigatorExample = require('./ReactNavigator/ReactNavigatorExample'); +var { TestModule } = React.addons; + var createExamplePage = require('./createExamplePage'); var COMPONENTS = [ @@ -69,6 +72,33 @@ var ds = new ListView.DataSource({ sectionHeaderHasChanged: (h1, h2) => h1 !== h2, }); +function makeRenderable(example: any): ReactClass { + return example.examples ? + createExamplePage(null, example) : + example; +} + +// Register suitable examples for snapshot tests +COMPONENTS.concat(APIS).forEach((Example) => { + if (Example.displayName) { + var Snapshotter = React.createClass({ + componentDidMount: function() { + // View is still blank after first RAF :\ + global.requestAnimationFrame(() => + global.requestAnimationFrame(() => TestModule.verifySnapshot( + TestModule.markTestCompleted + ) + )); + }, + render: function() { + var Renderable = makeRenderable(Example); + return ; + }, + }); + AppRegistry.registerComponent(Example.displayName, () => Snapshotter); + } +}); + class UIExplorerList extends React.Component { constructor(props) { @@ -152,9 +182,7 @@ class UIExplorerList extends React.Component { ); return; } - var Component = example.examples ? - createExamplePage(null, example) : - example; + var Component = makeRenderable(example); this.props.navigator.push({ title: Component.title, component: Component, diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testLayoutExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testLayoutExampleSnapshot_1@2x.png new file mode 100644 index 000000000..f90bf2c6a Binary files /dev/null and b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testLayoutExampleSnapshot_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testSliderExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testSliderExampleSnapshot_1@2x.png new file mode 100644 index 000000000..ee049f194 Binary files /dev/null and b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testSliderExampleSnapshot_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testSwitchExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testSwitchExampleSnapshot_1@2x.png new file mode 100644 index 000000000..a40558413 Binary files /dev/null and b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testSwitchExampleSnapshot_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTabBarExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTabBarExampleSnapshot_1@2x.png new file mode 100644 index 000000000..7237c5f10 Binary files /dev/null and b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTabBarExampleSnapshot_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png new file mode 100644 index 000000000..044576efc Binary files /dev/null and b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testViewExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testViewExampleSnapshot_1@2x.png new file mode 100644 index 000000000..4d16edfb2 Binary files /dev/null and b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testViewExampleSnapshot_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m index 624f441f9..b8fa57733 100644 --- a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m +++ b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m @@ -10,16 +10,36 @@ #import #import +#import + +#import "RCTAssert.h" #import "RCTRedBox.h" +#import "RCTRootView.h" #define TIMEOUT_SECONDS 240 @interface UIExplorerTests : XCTestCase +{ + RCTTestRunner *_runner; +} @end @implementation UIExplorerTests +- (void)setUp +{ +#ifdef __LP64__ + RCTAssert(!__LP64__, @"Snapshot tests should be run on 32-bit device simulators (e.g. iPhone 5)"); +#endif + NSString *version = [[UIDevice currentDevice] systemVersion]; + RCTAssert([version isEqualToString:@"8.1"], @"Snapshot tests should be run on iOS 8.1, found %@", version); + _runner = initRunnerForApp(@"Examples/UIExplorer/UIExplorerApp"); + + // If tests have changes, set recordMode = YES below and run the affected tests on an iPhone5, iOS 8.1 simulator. + _runner.recordMode = NO; +} + - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test { if (test(view)) { @@ -33,9 +53,10 @@ return NO; } -- (void)testRootViewLoadsAndRenders { +// Make sure this test runs first (underscores sort early) otherwise the other tests will tear out the rootView +- (void)test__RootViewLoadsAndRenders { UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - + RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first."); NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; BOOL foundElement = NO; NSString *redboxError = nil; @@ -61,5 +82,39 @@ XCTAssertTrue(foundElement, @"Cound't find element with '' text in %d seconds", TIMEOUT_SECONDS); } +- (void)testViewExampleSnapshot +{ + [_runner runTest:_cmd module:@"ViewExample"]; +} + +- (void)testLayoutExampleSnapshot +{ + [_runner runTest:_cmd module:@"LayoutExample"]; +} + +- (void)testTextExampleSnapshot +{ + [_runner runTest:_cmd module:@"TextExample"]; +} + +- (void)testSwitchExampleSnapshot +{ + [_runner runTest:_cmd module:@"SwitchExample"]; +} + +- (void)testSliderExampleSnapshot +{ + [_runner runTest:_cmd module:@"SliderExample"]; +} + +- (void)testTabBarExampleSnapshot +{ + [_runner runTest:_cmd module:@"TabBarExample"]; +} + +- (void)testZZZ_NotInRecordMode +{ + RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit."); +} @end diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index 03f025f20..88bbe0ee6 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -39,22 +39,26 @@ return; } - NSError *error = nil; - NSString *testName = NSStringFromSelector(_testSelector); - _snapshotCounter[testName] = @([_snapshotCounter[testName] integerValue] + 1); - BOOL success = [_snapshotController compareSnapshotOfView:_view - selector:_testSelector - identifier:[_snapshotCounter[testName] stringValue] - error:&error]; - RCTAssert(success, @"Snapshot comparison failed: %@", error); - callback(@[]); + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *testName = NSStringFromSelector(_testSelector); + _snapshotCounter[testName] = @([_snapshotCounter[testName] integerValue] + 1); + NSError *error = nil; + BOOL success = [_snapshotController compareSnapshotOfView:_view + selector:_testSelector + identifier:[_snapshotCounter[testName] stringValue] + error:&error]; + RCTAssert(success, @"Snapshot comparison failed: %@", error); + callback(@[]); + }); } - (void)markTestCompleted { RCT_EXPORT(); - _done = YES; + dispatch_async(dispatch_get_main_queue(), ^{ + _done = YES; + }); } @end