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,6 +24,10 @@ static NSString* const dismissOverlay	= @"dismissOverlay";
24 24
 static NSString* const mergeOptions	= @"mergeOptions";
25 25
 static NSString* const setDefaultOptions	= @"setDefaultOptions";
26 26
 
27
+@interface RNNCommandsHandler() <RNNModalManagerDelegate>
28
+
29
+@end
30
+
27 31
 @implementation RNNCommandsHandler {
28 32
 	RNNControllerFactory *_controllerFactory;
29 33
 	RNNStore *_store;
@@ -38,7 +42,8 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
38 42
 	_store = store;
39 43
 	_controllerFactory = controllerFactory;
40 44
 	_eventEmitter = eventEmitter;
41
-	_modalManager = [[RNNModalManager alloc] initWithStore:_store];
45
+	_modalManager = [[RNNModalManager alloc] init];
46
+	_modalManager.delegate = self;
42 47
 	_stackManager = [[RNNNavigationStackManager alloc] init];
43 48
 	_overlayManager = [[RNNOverlayManager alloc] init];
44 49
 	return self;
@@ -222,8 +227,8 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
222 227
 	[CATransaction setCompletionBlock:^{
223 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 233
 	[CATransaction commit];
229 234
 }
@@ -280,4 +285,16 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
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 300
 @end

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

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

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

@@ -92,6 +92,13 @@ static NSString* const SearchBarCancelPressed 	= @"RNN.SearchBarCancelPressed";
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 102
 - (void)addListener:(NSString *)eventName {
96 103
 	[super addListener:eventName];
97 104
 	if ([eventName isEqualToString:AppLaunched]) {

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

@@ -2,13 +2,20 @@
2 2
 #import <UIKit/UIKit.h>
3 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 12
 @interface RNNModalManager : NSObject
6 13
 
7 14
 @property (nonatomic, strong) UIViewController<RNNRootViewProtocol>* toVC;
15
+@property (nonatomic, weak) id<RNNModalManagerDelegate> delegate;
8 16
 
9
-- (instancetype)initWithStore:(RNNStore*)store;
10 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 19
 - (void)dismissAllModals;
13 20
 
14 21
 @end

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

@@ -2,14 +2,17 @@
2 2
 #import "RNNRootViewController.h"
3 3
 
4 4
 @implementation RNNModalManager {
5
-	RNNStore *_store;
6 5
 	RNNTransitionWithComponentIdCompletionBlock _completionBlock;
6
+	NSMutableArray* _pendingModalIdsToDismiss;
7
+	NSMutableArray* _presentedModals;
7 8
 }
8 9
 
9 10
 
10
--(instancetype)initWithStore:(RNNStore*)store {
11
+-(instancetype)init {
11 12
 	self = [super init];
12
-	_store = store;
13
+	_pendingModalIdsToDismiss = [[NSMutableArray alloc] init];
14
+	_presentedModals = [[NSMutableArray alloc] init];
15
+
13 16
 	return self;
14 17
 }
15 18
 
@@ -30,6 +33,9 @@
30 33
 			_completionBlock(self.toVC.getLeafViewController.componentId);
31 34
 			_completionBlock = nil;
32 35
 		}
36
+		
37
+		[_presentedModals addObject:self.toVC.getLeafViewController];
38
+		
33 39
 		self.toVC = nil;
34 40
 	}];
35 41
 }
@@ -55,23 +61,26 @@
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 71
 -(void)dismissAllModals {
64 72
 	UIViewController *root = UIApplication.sharedApplication.delegate.window.rootViewController;
65 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 79
 #pragma mark - private
70 80
 
71 81
 
72 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 84
 	RNNNavigationOptions* options = modalToDismiss.getLeafViewController.options;
76 85
 
77 86
 	if(!modalToDismiss) {
@@ -86,8 +95,10 @@
86 95
 
87 96
 	if (modalToDismiss == topPresentedVC || [[topPresentedVC childViewControllers] containsObject:modalToDismiss]) {
88 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 103
 			if (completion) {
93 104
 				completion();
@@ -98,12 +109,20 @@
98 109
 	} else {
99 110
 		[modalToDismiss.view removeFromSuperview];
100 111
 		modalToDismiss.view = nil;
112
+		modalToDismiss.getLeafViewController.options.animations.dismissModal.enable = NO;
113
+		[self dismissedModal:modalToDismiss];
114
+		
101 115
 		if (completion) {
102 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 126
 -(UIViewController*)topPresentedVC {
108 127
 	UIViewController *root = UIApplication.sharedApplication.delegate.window.rootViewController;
109 128
 	while(root.presentedViewController) {

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

@@ -23,8 +23,6 @@ typedef void (^RNNTransitionRejectionBlock)(NSString *code, NSString *message, N
23 23
 -(void) setReadyToReceiveCommands:(BOOL)isReady;
24 24
 -(BOOL) isReadyToReceiveCommands;
25 25
 
26
--(NSMutableArray*) pendingModalIdsToDismiss;
27
-
28 26
 -(void) clean;
29 27
 
30 28
 @end

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

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

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

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

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

@@ -1,22 +1,109 @@
1 1
 #import <XCTest/XCTest.h>
2 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 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 109
 @end

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

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