safe_mode_app_state_agent_unittest.mm 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #import "ios/chrome/app/safe_mode_app_state_agent.h"
  5. #include "base/ios/block_types.h"
  6. #include "base/test/ios/wait_util.h"
  7. #import "ios/chrome/app/application_delegate/app_state_testing.h"
  8. #import "ios/chrome/app/application_delegate/browser_launcher.h"
  9. #import "ios/chrome/app/application_delegate/startup_information.h"
  10. #import "ios/chrome/app/main_application_delegate.h"
  11. #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
  12. #import "ios/chrome/browser/ui/main/connection_information.h"
  13. #import "ios/chrome/browser/ui/main/test/fake_scene_state.h"
  14. #import "ios/chrome/browser/ui/safe_mode/safe_mode_coordinator.h"
  15. #include "ios/chrome/test/block_cleanup_test.h"
  16. #import "ios/chrome/test/scoped_key_window.h"
  17. #import "ios/testing/scoped_block_swizzler.h"
  18. #include "ios/web/public/test/web_task_environment.h"
  19. #import "third_party/ocmock/OCMock/OCMock.h"
  20. #include "third_party/ocmock/gtest_support.h"
  21. #if !defined(__has_feature) || !__has_feature(objc_arc)
  22. #error "This file requires ARC support."
  23. #endif
  24. namespace {
  25. // A block that takes self as argument and return a BOOL.
  26. typedef BOOL (^DecisionBlock)(id self);
  27. }
  28. // Exposes private methods for testing.
  29. @interface SafeModeAppAgent (Private)
  30. @property(nonatomic, strong) SafeModeCoordinator* safeModeCoordinator;
  31. @property(nonatomic, assign) BOOL firstSceneHasActivated;
  32. - (void)appState:(AppState*)appState
  33. didTransitionFromInitStage:(InitStage)previousStage;
  34. - (void)sceneState:(SceneState*)sceneState
  35. transitionedToActivationLevel:(SceneActivationLevel)level;
  36. - (void)coordinatorDidExitSafeMode:(SafeModeCoordinator*)coordinator;
  37. @end
  38. // Exposes private methods for testing.
  39. @interface AppState (Private)
  40. @property(nonatomic, assign) InitStage initStage;
  41. - (void)appState:(AppState*)appState
  42. didTransitionFromInitStage:(InitStage)previousInitStage;
  43. @end
  44. // Iterate through the init stages from `startInitStage` up to
  45. // `initStageDestination`.
  46. void IterateToStage(InitStage startInitStage,
  47. InitStage initStageDestination,
  48. SafeModeAppAgent* agent,
  49. id appStateMock) {
  50. InitStage initStage = startInitStage;
  51. if (initStage == InitStageStart) {
  52. [appStateMock setInitStage:InitStageStart];
  53. [agent appState:appStateMock didTransitionFromInitStage:InitStageStart];
  54. initStage = static_cast<InitStage>(startInitStage + 1);
  55. }
  56. InitStage prevInitStage = static_cast<InitStage>(initStage - 1);
  57. while (initStage <= initStageDestination) {
  58. [appStateMock setInitStage:initStage];
  59. [agent appState:appStateMock didTransitionFromInitStage:prevInitStage];
  60. prevInitStage = initStage;
  61. initStage = static_cast<InitStage>(initStage + 1);
  62. }
  63. }
  64. class SafeModeAppStateAgentTest : public BlockCleanupTest {
  65. protected:
  66. SafeModeAppStateAgentTest() {
  67. browser_state_ = TestChromeBrowserState::Builder().Build();
  68. window_ = [OCMockObject mockForClass:[UIWindow class]];
  69. browser_launcher_mock_ =
  70. [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
  71. startup_information_mock_ =
  72. [OCMockObject mockForProtocol:@protocol(StartupInformation)];
  73. connection_information_mock_ =
  74. [OCMockObject mockForProtocol:@protocol(ConnectionInformation)];
  75. main_application_delegate_ =
  76. [OCMockObject mockForClass:[MainApplicationDelegate class]];
  77. }
  78. void swizzleSafeModeShouldStart(BOOL shouldStart) {
  79. safe_mode_swizzle_block_ = ^BOOL(id self) {
  80. return shouldStart;
  81. };
  82. safe_mode_swizzler_.reset(new ScopedBlockSwizzler(
  83. [SafeModeCoordinator class], @selector(shouldStart),
  84. safe_mode_swizzle_block_));
  85. }
  86. AppState* getAppStateWithMock() {
  87. if (!app_state_) {
  88. // The swizzle block needs the scene state before app_state is created,
  89. // but the scene state needs the app state. So this alloc before swizzling
  90. // and initiate after app state is created.
  91. main_scene_state_ = [FakeSceneState alloc];
  92. app_state_ =
  93. [[AppState alloc] initWithBrowserLauncher:browser_launcher_mock_
  94. startupInformation:startup_information_mock_
  95. applicationDelegate:main_application_delegate_];
  96. main_scene_state_ =
  97. [main_scene_state_ initWithAppState:app_state_
  98. browserState:browser_state_.get()];
  99. main_scene_state_.window = getWindowMock();
  100. }
  101. return app_state_;
  102. }
  103. id getWindowMock() { return window_; }
  104. id getBrowserLauncherMock() { return browser_launcher_mock_; }
  105. FakeSceneState* GetSceneState() { return main_scene_state_; }
  106. private:
  107. web::WebTaskEnvironment task_environment_;
  108. std::unique_ptr<TestChromeBrowserState> browser_state_;
  109. AppState* app_state_;
  110. FakeSceneState* main_scene_state_;
  111. std::unique_ptr<ScopedBlockSwizzler> safe_mode_swizzler_;
  112. DecisionBlock safe_mode_swizzle_block_;
  113. id browser_launcher_mock_;
  114. id startup_information_mock_;
  115. id connection_information_mock_;
  116. id main_application_delegate_;
  117. id window_;
  118. };
  119. TEST_F(SafeModeAppStateAgentTest, startSafeMode) {
  120. id windowMock = getWindowMock();
  121. [[windowMock expect] makeKeyAndVisible];
  122. [[[windowMock stub] andReturn:nil] rootViewController];
  123. [[windowMock stub] setRootViewController:[OCMArg any]];
  124. AppState* appState = getAppStateWithMock();
  125. swizzleSafeModeShouldStart(YES);
  126. SafeModeAppAgent* agent = [[SafeModeAppAgent alloc] init];
  127. [agent setAppState:appState];
  128. IterateToStage(InitStageStart, InitStageSafeMode, agent, appState);
  129. SceneState* sceneState = GetSceneState();
  130. [agent sceneState:sceneState
  131. transitionedToActivationLevel:SceneActivationLevelForegroundActive];
  132. // Second call that should be deduped.
  133. [agent sceneState:sceneState
  134. transitionedToActivationLevel:SceneActivationLevelForegroundActive];
  135. EXPECT_OCMOCK_VERIFY(windowMock);
  136. // Exit safe mode.
  137. [agent coordinatorDidExitSafeMode:agent.safeModeCoordinator];
  138. EXPECT_EQ(nil, agent.safeModeCoordinator);
  139. }
  140. TEST_F(SafeModeAppStateAgentTest, dontStartSafeModeBecauseNotNeeded) {
  141. AppState* appState = getAppStateWithMock();
  142. id appStateMock = OCMPartialMock(appState);
  143. [[appStateMock expect] queueTransitionToNextInitStage];
  144. [[appStateMock expect] appState:appState
  145. didTransitionFromInitStage:InitStageSafeMode];
  146. swizzleSafeModeShouldStart(NO);
  147. SafeModeAppAgent* agent = [[SafeModeAppAgent alloc] init];
  148. [agent setAppState:appStateMock];
  149. IterateToStage(InitStageStart, InitStageSafeMode, agent, appStateMock);
  150. [agent sceneState:GetSceneState()
  151. transitionedToActivationLevel:SceneActivationLevelForegroundActive];
  152. EXPECT_OCMOCK_VERIFY(appStateMock);
  153. }
  154. TEST_F(SafeModeAppStateAgentTest, dontStartSafeModeBecauseNotActiveLevel) {
  155. AppState* appState = getAppStateWithMock();
  156. id appStateMock = OCMPartialMock(appState);
  157. [[appStateMock reject] queueTransitionToNextInitStage];
  158. swizzleSafeModeShouldStart(YES);
  159. SafeModeAppAgent* agent = [[SafeModeAppAgent alloc] init];
  160. [agent setAppState:appStateMock];
  161. IterateToStage(InitStageStart, InitStageSafeMode, agent, appStateMock);
  162. [agent sceneState:GetSceneState()
  163. transitionedToActivationLevel:SceneActivationLevelForegroundInactive];
  164. EXPECT_OCMOCK_VERIFY(appStateMock);
  165. }
  166. TEST_F(SafeModeAppStateAgentTest,
  167. dontStartSafeModeBecauseFirstSceneHasAlreadyActivated) {
  168. AppState* appState = getAppStateWithMock();
  169. id appStateMock = OCMPartialMock(appState);
  170. [[appStateMock reject] queueTransitionToNextInitStage];
  171. swizzleSafeModeShouldStart(YES);
  172. SafeModeAppAgent* agent = [[SafeModeAppAgent alloc] init];
  173. [agent setAppState:appStateMock];
  174. IterateToStage(InitStageStart, InitStageSafeMode, agent, appStateMock);
  175. agent.firstSceneHasActivated = YES;
  176. [agent sceneState:GetSceneState()
  177. transitionedToActivationLevel:SceneActivationLevelForegroundActive];
  178. EXPECT_OCMOCK_VERIFY(appStateMock);
  179. }