diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 00e971372..315371f4a 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -12,6 +12,7 @@ #import #import #import +#import #import #import "FBSnapshotTestController.h" @@ -113,6 +114,7 @@ configurationBlock:(void(^)(RCTRootView *rootView))configurationBlock expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock { __weak RCTBridge *batchedBridge; + NSNumber *rootTag; @autoreleasepool { __block NSMutableArray *errors = nil; @@ -133,36 +135,43 @@ expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock [bridge.devSettings setIsDebuggingRemotely:_useJSDebugger]; batchedBridge = [bridge batchedBridge]; - RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps]; -#if TARGET_OS_TV - rootView.frame = CGRectMake(0, 0, 1920, 1080); // Standard screen size for tvOS -#else - rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices -#endif + UIViewController *vc = RCTSharedApplication().delegate.window.rootViewController; + vc.view = [UIView new]; - RCTTestModule *testModule = [rootView.bridge moduleForClass:[RCTTestModule class]]; + RCTTestModule *testModule = [bridge moduleForClass:[RCTTestModule class]]; RCTAssert(_testController != nil, @"_testController should not be nil"); testModule.controller = _testController; testModule.testSelector = test; testModule.testSuffix = _testSuffix; - testModule.view = rootView; - UIViewController *vc = RCTSharedApplication().delegate.window.rootViewController; - vc.view = [UIView new]; - [vc.view addSubview:rootView]; // Add as subview so it doesn't get resized + @autoreleasepool { + // The rootView needs to be deallocated after this @autoreleasepool block exits. + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps]; +#if TARGET_OS_TV + rootView.frame = CGRectMake(0, 0, 1920, 1080); // Standard screen size for tvOS +#else + rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices +#endif - if (configurationBlock) { - configurationBlock(rootView); + rootTag = rootView.reactTag; + testModule.view = rootView; + + [vc.view addSubview:rootView]; // Add as subview so it doesn't get resized + + if (configurationBlock) { + configurationBlock(rootView); + } + + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:kTestTimeoutSeconds]; + while (date.timeIntervalSinceNow > 0 && testModule.status == RCTTestStatusPending && errors == nil) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } + + [rootView removeFromSuperview]; + testModule.view = nil; } - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:kTestTimeoutSeconds]; - while (date.timeIntervalSinceNow > 0 && testModule.status == RCTTestStatusPending && errors == nil) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - } - - [rootView removeFromSuperview]; - RCTSetLogFunction(defaultLogFunction); #if RCT_DEV @@ -181,15 +190,25 @@ expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock RCTAssert(testModule.status == RCTTestStatusPassed, @"Test failed"); } + // Wait for the rootView to be deallocated completely before invalidating the bridge. + RCTUIManager *uiManager = [bridge moduleForClass:[RCTUIManager class]]; + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:5]; + while (date.timeIntervalSinceNow > 0 && [uiManager viewForReactTag:rootTag]) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } + RCTAssert([uiManager viewForReactTag:rootTag] == nil, @"RootView should have been deallocated after removed."); + [bridge invalidate]; } - // Give the bridge a chance to disappear before continuing to the next test. + // Wait for the bridge to disappear before continuing to the next test. NSDate *invalidateTimeout = [NSDate dateWithTimeIntervalSinceNow:30]; while (invalidateTimeout.timeIntervalSinceNow > 0 && batchedBridge != nil) { [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; } + RCTAssert(batchedBridge == nil, @"Bridge should be deallocated after the test"); } @end