iOS: RCTTestRunner should deallocate rootview before invalidating the bridge
Summary: There are cases of race condition where the react component being mounted is calling a nativemodule from JS *right after* the test runner starts invalidating the bridge. This causes assertion failure deep in the RCTModuleData such that the bridge doesn't complete the invalidation. To avoid this, unmount and deallocate the RCTRootView before invalidating the bridge. Reviewed By: sahrens Differential Revision: D7727249 fbshipit-source-id: 8b82edc3b795ceb2e32441f16e225d723fcd9be1
This commit is contained in:
parent
37d28be2d9
commit
9909a4243f
|
@ -12,6 +12,7 @@
|
|||
#import <React/RCTDevSettings.h>
|
||||
#import <React/RCTLog.h>
|
||||
#import <React/RCTRootView.h>
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
#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<NSString *> *errors = nil;
|
||||
|
@ -133,6 +135,17 @@ expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
|||
[bridge.devSettings setIsDebuggingRemotely:_useJSDebugger];
|
||||
batchedBridge = [bridge batchedBridge];
|
||||
|
||||
UIViewController *vc = RCTSharedApplication().delegate.window.rootViewController;
|
||||
vc.view = [UIView new];
|
||||
|
||||
RCTTestModule *testModule = [bridge moduleForClass:[RCTTestModule class]];
|
||||
RCTAssert(_testController != nil, @"_testController should not be nil");
|
||||
testModule.controller = _testController;
|
||||
testModule.testSelector = test;
|
||||
testModule.testSuffix = _testSuffix;
|
||||
|
||||
@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
|
||||
|
@ -140,15 +153,9 @@ expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
|||
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
|
||||
#endif
|
||||
|
||||
RCTTestModule *testModule = [rootView.bridge moduleForClass:[RCTTestModule class]];
|
||||
RCTAssert(_testController != nil, @"_testController should not be nil");
|
||||
testModule.controller = _testController;
|
||||
testModule.testSelector = test;
|
||||
testModule.testSuffix = _testSuffix;
|
||||
rootTag = rootView.reactTag;
|
||||
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
|
||||
|
||||
if (configurationBlock) {
|
||||
|
@ -162,6 +169,8 @@ expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
|||
}
|
||||
|
||||
[rootView removeFromSuperview];
|
||||
testModule.view = nil;
|
||||
}
|
||||
|
||||
RCTSetLogFunction(defaultLogFunction);
|
||||
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue