chromium/ios/chrome/browser/lens_overlay/coordinator/lens_result_page_mediator_unittest.mm

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/lens_overlay/coordinator/lens_result_page_mediator.h"

#import "base/memory/raw_ptr.h"
#import "base/test/scoped_feature_list.h"
#import "ios/chrome/browser/lens_overlay/ui/lens_result_page_consumer.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/fake_authentication_service_delegate.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/navigation/web_state_policy_decider.h"
#import "ios/web/public/navigation/web_state_policy_decider_bridge.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/fake_navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_frames_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/fakes/fake_web_state_delegate.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

@interface LensResultPageMediator (Testing)
- (std::unique_ptr<web::WebState>)detachWebState;
- (void)attachWebState:(std::unique_ptr<web::WebState>)webState;
@end

namespace {

class LensResultPageMediatorTest : public PlatformTest {
 public:
  LensResultPageMediatorTest() {
    // AuthenticationService in required in AttachTabHelpers.
    TestChromeBrowserState::Builder builder;
    builder.AddTestingFactory(
        AuthenticationServiceFactory::GetInstance(),
        AuthenticationServiceFactory::GetDefaultFactory());
    browser_state_ = std::move(builder).Build();
    AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
        browser_state_.get(),
        std::make_unique<FakeAuthenticationServiceDelegate>());

    web::WebState::CreateParams params(browser_state_.get());
    mediator_ = [[LensResultPageMediator alloc]
         initWithWebStateParams:params
        browserWebStateDelegate:&browser_web_state_delegate_
                    isIncognito:NO];

    mock_consumer_ =
        [OCMockObject niceMockForProtocol:@protocol(LensResultPageConsumer)];
    mock_application_handler_ =
        [OCMockObject mockForProtocol:@protocol(ApplicationCommands)];

    mediator_.consumer = mock_consumer_;
    mediator_.applicationHandler = mock_application_handler_;
  }

  ~LensResultPageMediatorTest() override {
    [mediator_ disconnect];
    PlatformTest::TearDown();
  }

  // Tests whether the navigation is allowed by the policy provider.
  [[nodiscard]] bool TestShouldAllowRequest(NSString* url_string,
                                            bool target_frame_is_main) {
    NSURL* url = [NSURL URLWithString:url_string];
    const web::WebStatePolicyDecider::RequestInfo request_info(
        ui::PageTransition::PAGE_TRANSITION_LINK, target_frame_is_main,
        /*target_frame_is_cross_origin=*/false,
        /*target_window_is_cross_origin=*/false,
        /*is_user_initiated=*/true,
        /*user_tapped_recently=*/false);
    __block bool callback_called = false;
    __block web::WebStatePolicyDecider::PolicyDecision policy_decision =
        web::WebStatePolicyDecider::PolicyDecision::Allow();
    auto callback = ^(web::WebStatePolicyDecider::PolicyDecision decision) {
      policy_decision = decision;
      callback_called = true;
    };

    if ([mediator_ conformsToProtocol:@protocol(CRWWebStatePolicyDecider)]) {
      id<CRWWebStatePolicyDecider> policy_decider =
          static_cast<id<CRWWebStatePolicyDecider>>(mediator_);
      [policy_decider shouldAllowRequest:[NSURLRequest requestWithURL:url]
                             requestInfo:request_info
                         decisionHandler:std::move(callback)];
    }
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(callback_called);
    return policy_decision.ShouldAllowNavigation();
  }

  // Replaces the web state from LensResultPageMediator with a fake one.
  void AttachFakeWebState() {
    auto web_state = std::make_unique<web::FakeWebState>();
    web_state->SetBrowserState(browser_state_.get());
    web_state->SetIsRealized(true);
    web_state->SetWebFramesManager(
        web::ContentWorld::kAllContentWorlds,
        std::make_unique<web::FakeWebFramesManager>());
    web_state->SetWebFramesManager(
        web::ContentWorld::kPageContentWorld,
        std::make_unique<web::FakeWebFramesManager>());
    web_state->SetWebFramesManager(
        web::ContentWorld::kIsolatedWorld,
        std::make_unique<web::FakeWebFramesManager>());
    web_state->SetNavigationManager(
        std::make_unique<web::FakeNavigationManager>());
    fake_web_state_ = web_state.get();
    [mediator_ detachWebState];
    [mediator_ attachWebState:std::move(web_state)];
  }

 protected:
  web::WebTaskEnvironment task_environment_;
  IOSChromeScopedTestingLocalState scoped_testing_local_state_;

  LensResultPageMediator* mediator_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  web::FakeWebStateDelegate browser_web_state_delegate_;
  OCMockObject<LensResultPageConsumer>* mock_consumer_;
  OCMockObject<ApplicationCommands>* mock_application_handler_;

  // Call `AttachFakeWebState()` to use `fake_web_state_`.
  web::FakeWebState* fake_web_state_;
};

// Tests that the mediator starts a navigation when loadResultsURL is called.
TEST_F(LensResultPageMediatorTest, ShouldStartNavigationWhenLoadingResultsURL) {
  GURL result_url = GURL("https://www.google.com");
  [mediator_ loadResultsURL:result_url];

  EXPECT_EQ(browser_web_state_delegate_.last_open_url_request()->params.url,
            result_url);
}

// Tests that web navigation to google is allowed.
TEST_F(LensResultPageMediatorTest, ShouldAllowGoogleNavigation) {
  EXPECT_TRUE(TestShouldAllowRequest(@"https://www.google.com",
                                     /*target_frame_is_main=*/true));
}

// Tests that other navigation is not allowed but opens a new tab.
TEST_F(LensResultPageMediatorTest, ShouldOpenOtherNavigationInNewTab) {
  OCMExpect([mock_application_handler_ openURLInNewTab:[OCMArg any]]);
  EXPECT_FALSE(TestShouldAllowRequest(@"https://www.chromium.com",
                                      /*target_frame_is_main=*/true));
  EXPECT_OCMOCK_VERIFY(mock_application_handler_);
}

// Tests that any navigation that's not on main frame is allowed.
TEST_F(LensResultPageMediatorTest, ShouldAllowAnyNavigationNotInMainFrame) {
  EXPECT_TRUE(TestShouldAllowRequest(@"https://www.chromium.com",
                                     /*target_frame_is_main=*/false));
  EXPECT_TRUE(TestShouldAllowRequest(@"https://www.google.com",
                                     /*target_frame_is_main=*/false));
}

// Tests that updating the background color calls the consumer.
TEST_F(LensResultPageMediatorTest, BackgroundColorUpdates) {
  AttachFakeWebState();

  // Make the consumer mock strict.
  mock_consumer_ =
      [OCMockObject mockForProtocol:@protocol(LensResultPageConsumer)];
  OCMExpect([mock_consumer_ setWebView:[OCMArg any]]);
  mediator_.consumer = mock_consumer_;

  // Background color is not updated if nil.
  fake_web_state_->SetUnderPageBackgroundColor(nil);

  // Background color is updated when it changes.
  OCMExpect([mock_consumer_ setBackgroundColor:UIColor.blackColor]);
  fake_web_state_->SetUnderPageBackgroundColor(UIColor.blackColor);
  EXPECT_OCMOCK_VERIFY(mock_consumer_);

  // Background color is updated when the navigation finishes.
  OCMExpect([mock_consumer_ setBackgroundColor:UIColor.blackColor]);
  web::FakeNavigationContext context;
  fake_web_state_->OnNavigationFinished(&context);
  EXPECT_OCMOCK_VERIFY(mock_consumer_);
}

}  // namespace