Browse Source

Refactored modals (#3917)

* Refactored modals

* Added unit tests for modals

* fixes unknown modal dismissing and added tests
Yogev Ben David 6 years ago
parent
commit
021c169a1f
No account linked to committer's email address

+ 20
- 3
lib/ios/RNNCommandsHandler.m View File

24
 static NSString* const mergeOptions	= @"mergeOptions";
24
 static NSString* const mergeOptions	= @"mergeOptions";
25
 static NSString* const setDefaultOptions	= @"setDefaultOptions";
25
 static NSString* const setDefaultOptions	= @"setDefaultOptions";
26
 
26
 
27
+@interface RNNCommandsHandler() <RNNModalManagerDelegate>
28
+
29
+@end
30
+
27
 @implementation RNNCommandsHandler {
31
 @implementation RNNCommandsHandler {
28
 	RNNControllerFactory *_controllerFactory;
32
 	RNNControllerFactory *_controllerFactory;
29
 	RNNStore *_store;
33
 	RNNStore *_store;
38
 	_store = store;
42
 	_store = store;
39
 	_controllerFactory = controllerFactory;
43
 	_controllerFactory = controllerFactory;
40
 	_eventEmitter = eventEmitter;
44
 	_eventEmitter = eventEmitter;
41
-	_modalManager = [[RNNModalManager alloc] initWithStore:_store];
45
+	_modalManager = [[RNNModalManager alloc] init];
46
+	_modalManager.delegate = self;
42
 	_stackManager = [[RNNNavigationStackManager alloc] init];
47
 	_stackManager = [[RNNNavigationStackManager alloc] init];
43
 	_overlayManager = [[RNNOverlayManager alloc] init];
48
 	_overlayManager = [[RNNOverlayManager alloc] init];
44
 	return self;
49
 	return self;
222
 	[CATransaction setCompletionBlock:^{
227
 	[CATransaction setCompletionBlock:^{
223
 		[_eventEmitter sendOnNavigationCommandCompletion:dismissModal params:@{@"componentId": componentId}];
228
 		[_eventEmitter sendOnNavigationCommandCompletion:dismissModal params:@{@"componentId": componentId}];
224
 	}];
229
 	}];
225
-	
226
-	[_modalManager dismissModal:componentId completion:completion];
230
+	UIViewController<RNNRootViewProtocol> *modalToDismiss = (UIViewController<RNNRootViewProtocol>*)[_store findComponentForId:componentId];
231
+	[_modalManager dismissModal:modalToDismiss completion:completion];
227
 	
232
 	
228
 	[CATransaction commit];
233
 	[CATransaction commit];
229
 }
234
 }
280
 	}
285
 	}
281
 }
286
 }
282
 
287
 
288
+#pragma mark - RNNModalManagerDelegate
289
+
290
+- (void)dismissedModal:(UIViewController *)viewController {
291
+	[_eventEmitter sendModalsDismissedEvent:((RNNRootViewController *)viewController).componentId numberOfModalsDismissed:@(1)];
292
+}
293
+
294
+- (void)dismissedMultipleModals:(NSArray *)viewControllers {
295
+	if (viewControllers && viewControllers.count) {
296
+		[_eventEmitter sendModalsDismissedEvent:((RNNRootViewController *)viewControllers.lastObject).componentId numberOfModalsDismissed:@(viewControllers.count)];
297
+	}
298
+}
299
+
283
 @end
300
 @end

+ 2
- 0
lib/ios/RNNEventEmitter.h View File

22
 
22
 
23
 -(void)sendOnSearchBarCancelPressed:(NSString *)componentId;
23
 -(void)sendOnSearchBarCancelPressed:(NSString *)componentId;
24
 
24
 
25
+- (void)sendModalsDismissedEvent:(NSString *)componentId numberOfModalsDismissed:(NSNumber *)modalsDismissed;
26
+
25
 @end
27
 @end

+ 7
- 0
lib/ios/RNNEventEmitter.m View File

92
 											}];
92
 											}];
93
 }
93
 }
94
 
94
 
95
+- (void)sendModalsDismissedEvent:(NSString *)componentId numberOfModalsDismissed:(NSNumber *)modalsDismissed {
96
+	[self send:ModalDismissed body:@{
97
+											 @"componentId": componentId,
98
+											 @"modalsDismissed": modalsDismissed
99
+											 }];
100
+}
101
+
95
 - (void)addListener:(NSString *)eventName {
102
 - (void)addListener:(NSString *)eventName {
96
 	[super addListener:eventName];
103
 	[super addListener:eventName];
97
 	if ([eventName isEqualToString:AppLaunched]) {
104
 	if ([eventName isEqualToString:AppLaunched]) {

+ 9
- 2
lib/ios/RNNModalManager.h View File

2
 #import <UIKit/UIKit.h>
2
 #import <UIKit/UIKit.h>
3
 #import "RNNStore.h"
3
 #import "RNNStore.h"
4
 
4
 
5
+@protocol RNNModalManagerDelegate <NSObject>
6
+
7
+- (void)dismissedModal:(UIViewController *)viewController;
8
+- (void)dismissedMultipleModals:(NSArray *)viewControllers;
9
+
10
+@end
11
+
5
 @interface RNNModalManager : NSObject
12
 @interface RNNModalManager : NSObject
6
 
13
 
7
 @property (nonatomic, strong) UIViewController<RNNRootViewProtocol>* toVC;
14
 @property (nonatomic, strong) UIViewController<RNNRootViewProtocol>* toVC;
15
+@property (nonatomic, weak) id<RNNModalManagerDelegate> delegate;
8
 
16
 
9
-- (instancetype)initWithStore:(RNNStore*)store;
10
 - (void)showModal:(UIViewController*)viewController animated:(BOOL)animated completion:(RNNTransitionWithComponentIdCompletionBlock)completion;
17
 - (void)showModal:(UIViewController*)viewController animated:(BOOL)animated completion:(RNNTransitionWithComponentIdCompletionBlock)completion;
11
-- (void)dismissModal:(NSString *)componentId completion:(RNNTransitionCompletionBlock)completion;
18
+- (void)dismissModal:(UIViewController *)viewController completion:(RNNTransitionCompletionBlock)completion;
12
 - (void)dismissAllModals;
19
 - (void)dismissAllModals;
13
 
20
 
14
 @end
21
 @end

+ 30
- 11
lib/ios/RNNModalManager.m View File

2
 #import "RNNRootViewController.h"
2
 #import "RNNRootViewController.h"
3
 
3
 
4
 @implementation RNNModalManager {
4
 @implementation RNNModalManager {
5
-	RNNStore *_store;
6
 	RNNTransitionWithComponentIdCompletionBlock _completionBlock;
5
 	RNNTransitionWithComponentIdCompletionBlock _completionBlock;
6
+	NSMutableArray* _pendingModalIdsToDismiss;
7
+	NSMutableArray* _presentedModals;
7
 }
8
 }
8
 
9
 
9
 
10
 
10
--(instancetype)initWithStore:(RNNStore*)store {
11
+-(instancetype)init {
11
 	self = [super init];
12
 	self = [super init];
12
-	_store = store;
13
+	_pendingModalIdsToDismiss = [[NSMutableArray alloc] init];
14
+	_presentedModals = [[NSMutableArray alloc] init];
15
+
13
 	return self;
16
 	return self;
14
 }
17
 }
15
 
18
 
30
 			_completionBlock(self.toVC.getLeafViewController.componentId);
33
 			_completionBlock(self.toVC.getLeafViewController.componentId);
31
 			_completionBlock = nil;
34
 			_completionBlock = nil;
32
 		}
35
 		}
36
+		
37
+		[_presentedModals addObject:self.toVC.getLeafViewController];
38
+		
33
 		self.toVC = nil;
39
 		self.toVC = nil;
34
 	}];
40
 	}];
35
 }
41
 }
55
 	}
61
 	}
56
 }
62
 }
57
 
63
 
58
-- (void)dismissModal:(NSString *)componentId completion:(RNNTransitionCompletionBlock)completion {
59
-	[[_store pendingModalIdsToDismiss] addObject:componentId];
60
-	[self removePendingNextModalIfOnTop:completion];
64
+- (void)dismissModal:(UIViewController *)viewController completion:(RNNTransitionCompletionBlock)completion {
65
+	if (viewController) {
66
+		[_pendingModalIdsToDismiss addObject:viewController];
67
+		[self removePendingNextModalIfOnTop:completion];
68
+	}
61
 }
69
 }
62
 
70
 
63
 -(void)dismissAllModals {
71
 -(void)dismissAllModals {
64
 	UIViewController *root = UIApplication.sharedApplication.delegate.window.rootViewController;
72
 	UIViewController *root = UIApplication.sharedApplication.delegate.window.rootViewController;
65
 	[root dismissViewControllerAnimated:YES completion:nil];
73
 	[root dismissViewControllerAnimated:YES completion:nil];
66
-	[[_store pendingModalIdsToDismiss] removeAllObjects];
74
+	[_delegate dismissedMultipleModals:_presentedModals];
75
+	[_pendingModalIdsToDismiss removeAllObjects];
76
+	[_presentedModals removeAllObjects];
67
 }
77
 }
68
 
78
 
69
 #pragma mark - private
79
 #pragma mark - private
70
 
80
 
71
 
81
 
72
 -(void)removePendingNextModalIfOnTop:(RNNTransitionCompletionBlock)completion {
82
 -(void)removePendingNextModalIfOnTop:(RNNTransitionCompletionBlock)completion {
73
-	NSString *componentId = [[_store pendingModalIdsToDismiss] lastObject];
74
-	UIViewController<RNNRootViewProtocol> *modalToDismiss = (UIViewController<RNNRootViewProtocol>*)[_store findComponentForId:componentId];
83
+	UIViewController<RNNRootViewProtocol> *modalToDismiss = [_pendingModalIdsToDismiss lastObject];
75
 	RNNNavigationOptions* options = modalToDismiss.getLeafViewController.options;
84
 	RNNNavigationOptions* options = modalToDismiss.getLeafViewController.options;
76
 
85
 
77
 	if(!modalToDismiss) {
86
 	if(!modalToDismiss) {
86
 
95
 
87
 	if (modalToDismiss == topPresentedVC || [[topPresentedVC childViewControllers] containsObject:modalToDismiss]) {
96
 	if (modalToDismiss == topPresentedVC || [[topPresentedVC childViewControllers] containsObject:modalToDismiss]) {
88
 		[modalToDismiss dismissViewControllerAnimated:options.animations.dismissModal.enable completion:^{
97
 		[modalToDismiss dismissViewControllerAnimated:options.animations.dismissModal.enable completion:^{
89
-			[[_store pendingModalIdsToDismiss] removeObject:componentId];
90
-			[_store removeComponent:componentId];
98
+			[_pendingModalIdsToDismiss removeObject:modalToDismiss];
99
+			if (modalToDismiss.view) {
100
+				[self dismissedModal:modalToDismiss];
101
+			}
91
 			
102
 			
92
 			if (completion) {
103
 			if (completion) {
93
 				completion();
104
 				completion();
98
 	} else {
109
 	} else {
99
 		[modalToDismiss.view removeFromSuperview];
110
 		[modalToDismiss.view removeFromSuperview];
100
 		modalToDismiss.view = nil;
111
 		modalToDismiss.view = nil;
112
+		modalToDismiss.getLeafViewController.options.animations.dismissModal.enable = NO;
113
+		[self dismissedModal:modalToDismiss];
114
+		
101
 		if (completion) {
115
 		if (completion) {
102
 			completion();
116
 			completion();
103
 		}
117
 		}
104
 	}
118
 	}
105
 }
119
 }
106
 
120
 
121
+- (void)dismissedModal:(UIViewController *)viewController {
122
+	[_presentedModals removeObject:viewController];
123
+	[_delegate dismissedModal:viewController];
124
+}
125
+
107
 -(UIViewController*)topPresentedVC {
126
 -(UIViewController*)topPresentedVC {
108
 	UIViewController *root = UIApplication.sharedApplication.delegate.window.rootViewController;
127
 	UIViewController *root = UIApplication.sharedApplication.delegate.window.rootViewController;
109
 	while(root.presentedViewController) {
128
 	while(root.presentedViewController) {

+ 0
- 2
lib/ios/RNNStore.h View File

23
 -(void) setReadyToReceiveCommands:(BOOL)isReady;
23
 -(void) setReadyToReceiveCommands:(BOOL)isReady;
24
 -(BOOL) isReadyToReceiveCommands;
24
 -(BOOL) isReadyToReceiveCommands;
25
 
25
 
26
--(NSMutableArray*) pendingModalIdsToDismiss;
27
-
28
 -(void) clean;
26
 -(void) clean;
29
 
27
 
30
 @end
28
 @end

+ 0
- 7
lib/ios/RNNStore.m View File

7
 
7
 
8
 @implementation RNNStore {
8
 @implementation RNNStore {
9
 	NSMapTable* _componentStore;
9
 	NSMapTable* _componentStore;
10
-	NSMutableArray* _pendingModalIdsToDismiss;
11
 	NSMutableDictionary* _externalComponentCreators;
10
 	NSMutableDictionary* _externalComponentCreators;
12
 	BOOL _isReadyToReceiveCommands;
11
 	BOOL _isReadyToReceiveCommands;
13
 }
12
 }
16
 	self = [super init];
15
 	self = [super init];
17
 	_isReadyToReceiveCommands = false;
16
 	_isReadyToReceiveCommands = false;
18
 	_componentStore = [NSMapTable strongToWeakObjectsMapTable];
17
 	_componentStore = [NSMapTable strongToWeakObjectsMapTable];
19
-	_pendingModalIdsToDismiss = [NSMutableArray new];
20
 	_externalComponentCreators = [NSMutableDictionary new];
18
 	_externalComponentCreators = [NSMutableDictionary new];
21
 	return self;
19
 	return self;
22
 }
20
 }
53
 	return _isReadyToReceiveCommands;
51
 	return _isReadyToReceiveCommands;
54
 }
52
 }
55
 
53
 
56
--(NSMutableArray *)pendingModalIdsToDismiss {
57
-	return _pendingModalIdsToDismiss;
58
-}
59
-
60
 -(void)clean {
54
 -(void)clean {
61
 	_isReadyToReceiveCommands = false;
55
 	_isReadyToReceiveCommands = false;
62
-	[_pendingModalIdsToDismiss removeAllObjects];
63
 	[_componentStore removeAllObjects];
56
 	[_componentStore removeAllObjects];
64
 }
57
 }
65
 
58
 

+ 2
- 0
lib/ios/ReactNativeNavigationTests/RNNCommandsHandlerTest.m View File

70
 	[skipMethods addObject:@"assertReady"];
70
 	[skipMethods addObject:@"assertReady"];
71
 	[skipMethods addObject:@"removePopedViewControllers:"];
71
 	[skipMethods addObject:@"removePopedViewControllers:"];
72
 	[skipMethods addObject:@".cxx_destruct"];
72
 	[skipMethods addObject:@".cxx_destruct"];
73
+	[skipMethods addObject:@"dismissedModal:"];
74
+	[skipMethods addObject:@"dismissedMultipleModals:"];
73
 
75
 
74
 	NSMutableArray* result = [NSMutableArray new];
76
 	NSMutableArray* result = [NSMutableArray new];
75
 
77
 

+ 97
- 10
lib/ios/ReactNativeNavigationTests/RNNModalManagerTest.m View File

1
 #import <XCTest/XCTest.h>
1
 #import <XCTest/XCTest.h>
2
 #import "RNNModalManager.h"
2
 #import "RNNModalManager.h"
3
 
3
 
4
-@interface RNNModalManagerTest : XCTestCase
4
+@interface MockViewController : UIViewController
5
+@end
6
+@implementation MockViewController
7
+
8
+- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
9
+	completion();
10
+}
11
+
12
+@end
13
+
14
+@interface MockModalManager : RNNModalManager
15
+@end
16
+@implementation MockModalManager
17
+
18
+-(UIViewController*)topPresentedVC {
19
+	MockViewController* vc = [MockViewController new];
20
+	return vc;
21
+}
5
 
22
 
6
 @end
23
 @end
7
 
24
 
8
-@implementation RNNModalManagerTest
25
+@interface RNNModalManagerTest : XCTestCase <RNNModalManagerDelegate> {
26
+	CGFloat _modalDismissedCount;
27
+}
28
+
29
+@end
30
+
31
+@implementation RNNModalManagerTest {
32
+	RNNRootViewController* _vc1;
33
+	RNNRootViewController* _vc2;
34
+	RNNRootViewController* _vc3;
35
+	MockModalManager* _modalManager;
36
+}
37
+
38
+- (void)setUp {
39
+	[super setUp];
40
+	_vc1 = [RNNRootViewController new];
41
+	_vc2 = [RNNRootViewController new];
42
+	_vc3 = [RNNRootViewController new];
43
+	_modalManager = [[MockModalManager alloc] init];
44
+}
45
+- (void)testDismissMultipleModalsInvokeDelegateWithCorrectParameters {
46
+	[_modalManager showModal:_vc1 animated:NO completion:nil];
47
+	[_modalManager showModal:_vc2 animated:NO completion:nil];
48
+	[_modalManager showModal:_vc3 animated:NO completion:nil];
49
+	
50
+	_modalManager.delegate = self;
51
+	[_modalManager dismissAllModals];
52
+	
53
+	XCTAssertTrue(_modalDismissedCount == 3);
54
+}
55
+
56
+- (void)testDismissModal_InvokeDelegateWithCorrectParameters {
57
+	[_modalManager showModal:_vc1 animated:NO completion:nil];
58
+	[_modalManager showModal:_vc2 animated:NO completion:nil];
59
+	[_modalManager showModal:_vc3 animated:NO completion:nil];
60
+	
61
+	_modalManager.delegate = self;
62
+	[_modalManager dismissModal:_vc3 completion:nil];
63
+	
64
+	XCTAssertTrue(_modalDismissedCount == 1);
65
+}
66
+
67
+- (void)testDismissPreviousModal_InvokeDelegateWithCorrectParameters {
68
+	[_modalManager showModal:_vc1 animated:NO completion:nil];
69
+	[_modalManager showModal:_vc2 animated:NO completion:nil];
70
+	[_modalManager showModal:_vc3 animated:NO completion:nil];
71
+	
72
+	_modalManager.delegate = self;
73
+	[_modalManager dismissModal:_vc2 completion:nil];
74
+	
75
+	XCTAssertTrue(_modalDismissedCount == 1);
76
+}
9
 
77
 
10
-- (void)testDismissAllModalsCleansPendingModalsToDismiss {
11
-	RNNStore *store = [RNNStore new];
12
-	[[store pendingModalIdsToDismiss] addObject:@"modal_id_1"];
13
-	[[store pendingModalIdsToDismiss] addObject:@"modal_id_2"];
14
-	[[store pendingModalIdsToDismiss] addObject:@"modal_id_3"];
78
+- (void)testDismissAllModalsAfterDismissingPreviousModal_InvokeDelegateWithCorrectParameters {
79
+	[_modalManager showModal:_vc1 animated:NO completion:nil];
80
+	[_modalManager showModal:_vc2 animated:NO completion:nil];
81
+	[_modalManager showModal:_vc3 animated:NO completion:nil];
15
 	
82
 	
16
-	RNNModalManager *modalManager = [[RNNModalManager alloc] initWithStore:store];
17
-	[modalManager dismissAllModals];
83
+	_modalManager.delegate = self;
84
+	[_modalManager dismissModal:_vc2 completion:nil];
18
 	
85
 	
19
-	XCTAssertTrue([[store pendingModalIdsToDismiss] count] == 0);
86
+	XCTAssertTrue(_modalDismissedCount == 1);
87
+	[_modalManager dismissAllModals];
88
+	XCTAssertTrue(_modalDismissedCount == 2);
89
+}
90
+
91
+- (void)testDismissNilModal_doesntCrash {
92
+	_modalManager.delegate = self;
93
+	[_modalManager dismissModal:nil completion:nil];
94
+	
95
+	XCTAssertTrue(_modalDismissedCount == 0);
96
+}
97
+
98
+
99
+#pragma mark RNNModalManagerDelegate
100
+
101
+- (void)dismissedMultipleModals:(NSArray *)viewControllers {
102
+	_modalDismissedCount = viewControllers.count;
103
+}
104
+
105
+- (void)dismissedModal:(UIViewController *)viewController {
106
+	_modalDismissedCount = 1;
20
 }
107
 }
21
 
108
 
22
 @end
109
 @end

+ 0
- 1
lib/ios/ReactNativeNavigationTests/RNNStoreTest.m View File

105
 
105
 
106
 -(void)testCleanStore {
106
 -(void)testCleanStore {
107
 	[self.store clean];
107
 	[self.store clean];
108
-	XCTAssertFalse(self.store.pendingModalIdsToDismiss.count);
109
 	XCTAssertFalse(self.store.isReadyToReceiveCommands);
108
 	XCTAssertFalse(self.store.isReadyToReceiveCommands);
110
 }
109
 }
111
 
110