chromium/ios/chrome/browser/contextual_panel/model/active_contextual_panel_tab_helper_observation_forwarder_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/contextual_panel/model/active_contextual_panel_tab_helper_observation_forwarder.h"

#import <memory>
#import <vector>

#import "base/containers/contains.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper_observer.h"
#import "ios/chrome/browser/shared/model/web_state_list/test/fake_web_state_list_delegate.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

namespace {

class TestObserver : public ContextualPanelTabHelperObserver {
 public:
  TestObserver() {}
  ~TestObserver() override {}

  bool WasInvokedFor(ContextualPanelTabHelper* tab_helper) {
    return base::Contains(invoker_tab_helpers_, tab_helper);
  }

  void Reset() { invoker_tab_helpers_.clear(); }

  // ContextualPanelTabHelperObserver.
  void ContextualPanelHasNewData(
      ContextualPanelTabHelper* tab_helper,
      std::vector<base::WeakPtr<ContextualPanelItemConfiguration>>
          item_configurations) override {
    invoker_tab_helpers_.push_back(tab_helper);
  }

 private:
  std::vector<ContextualPanelTabHelper*> invoker_tab_helpers_;
};

class ActiveContextualPanelTabHelperObservationForwarderTest
    : public PlatformTest {
 public:
  ActiveContextualPanelTabHelperObservationForwarderTest()
      : web_state_list_(&web_state_list_delegate_) {
    forwarder_ =
        std::make_unique<ActiveContextualPanelTabHelperObservationForwarder>(
            &web_state_list_, &observer_);
  }

  web::FakeWebState* AddWebStateToList(bool activate) {
    auto web_state = std::make_unique<web::FakeWebState>();
    web::FakeWebState* web_state_ptr = web_state.get();
    std::map<ContextualPanelItemType, raw_ptr<ContextualPanelModel>> models;
    ContextualPanelTabHelper::CreateForWebState(web_state_ptr, models);
    web_state_list_.InsertWebState(
        std::move(web_state),
        WebStateList::InsertionParams::Automatic().Activate(activate));
    return web_state_ptr;
  }

 protected:
  FakeWebStateListDelegate web_state_list_delegate_;
  WebStateList web_state_list_;
  TestObserver observer_;
  std::unique_ptr<ActiveContextualPanelTabHelperObservationForwarder>
      forwarder_;
};

}  // namespace

TEST_F(ActiveContextualPanelTabHelperObservationForwarderTest,
       TestInsertActiveWebState) {
  // Insert two webstates into the list and mark the second one active.  Send
  // tab helper observer notifications for both and verify the result.
  web::FakeWebState* web_state_a = AddWebStateToList(true);
  web::FakeWebState* web_state_b = AddWebStateToList(true);
  ASSERT_EQ(web_state_b, web_state_list_.GetActiveWebState());

  web::FakeNavigationContext context;
  web_state_a->OnNavigationStarted(&context);
  web_state_b->OnNavigationStarted(&context);

  // The observer should only be notified for the active tab helper B.
  ContextualPanelTabHelper* tab_helper_a =
      ContextualPanelTabHelper::FromWebState(web_state_a);
  ContextualPanelTabHelper* tab_helper_b =
      ContextualPanelTabHelper::FromWebState(web_state_b);
  EXPECT_TRUE(observer_.WasInvokedFor(tab_helper_b));
  EXPECT_FALSE(observer_.WasInvokedFor(tab_helper_a));
}

TEST_F(ActiveContextualPanelTabHelperObservationForwarderTest,
       TestInsertNonActiveWebState) {
  // Insert two webstates into the list, but do not mark the second one active.
  // Send tab helper observer notifications for both and verify the result.
  web::FakeWebState* web_state_a = AddWebStateToList(true);
  web::FakeWebState* web_state_b = AddWebStateToList(false);
  ASSERT_EQ(web_state_a, web_state_list_.GetActiveWebState());

  web::FakeNavigationContext context;
  web_state_a->OnNavigationStarted(&context);
  web_state_b->OnNavigationStarted(&context);

  // The observer should only be notified for the active web state A.
  ContextualPanelTabHelper* tab_helper_a =
      ContextualPanelTabHelper::FromWebState(web_state_a);
  ContextualPanelTabHelper* tab_helper_b =
      ContextualPanelTabHelper::FromWebState(web_state_b);
  EXPECT_TRUE(observer_.WasInvokedFor(tab_helper_a));
  EXPECT_FALSE(observer_.WasInvokedFor(tab_helper_b));
}

TEST_F(ActiveContextualPanelTabHelperObservationForwarderTest,
       TestDetachActiveWebState) {
  // Insert three webstates into the list.
  web::FakeWebState* web_state_a = AddWebStateToList(true);
  web::FakeWebState* web_state_b = AddWebStateToList(true);
  web::FakeWebState* web_state_c = AddWebStateToList(true);
  ASSERT_EQ(web_state_c, web_state_list_.GetActiveWebState());

  // Remove the active web state and send tab helper observer notifications.
  std::unique_ptr<web::WebState> detached_web_state =
      web_state_list_.DetachWebStateAt(web_state_list_.active_index());
  web::WebState* active_web_state = web_state_list_.GetActiveWebState();
  web::WebState* non_active_web_state =
      (active_web_state == web_state_a ? web_state_b : web_state_a);

  web::FakeNavigationContext context;
  web_state_a->OnNavigationStarted(&context);
  web_state_b->OnNavigationStarted(&context);
  web_state_c->OnNavigationStarted(&context);

  // The observer should only be notified for the new active web state.
  ContextualPanelTabHelper* active_tab_helper =
      ContextualPanelTabHelper::FromWebState(active_web_state);
  ContextualPanelTabHelper* non_active_tab_helper =
      ContextualPanelTabHelper::FromWebState(non_active_web_state);
  ContextualPanelTabHelper* tab_helper_c =
      ContextualPanelTabHelper::FromWebState(web_state_c);
  EXPECT_TRUE(observer_.WasInvokedFor(active_tab_helper));
  EXPECT_FALSE(observer_.WasInvokedFor(non_active_tab_helper));
  EXPECT_FALSE(observer_.WasInvokedFor(tab_helper_c));
}

TEST_F(ActiveContextualPanelTabHelperObservationForwarderTest,
       TestDetachNonActiveWebState) {
  // Insert three webstates into the list.
  web::FakeWebState* web_state_a = AddWebStateToList(true);
  web::FakeWebState* web_state_b = AddWebStateToList(true);
  web::FakeWebState* web_state_c = AddWebStateToList(true);
  ASSERT_EQ(web_state_c, web_state_list_.GetActiveWebState());

  // Remove a non-active web state and send tab helper observer notifications.
  std::unique_ptr<web::WebState> detached_web_state =
      web_state_list_.DetachWebStateAt(
          web_state_list_.GetIndexOfWebState(web_state_a));
  ASSERT_EQ(web_state_c, web_state_list_.GetActiveWebState());

  web::FakeNavigationContext context;
  web_state_a->OnNavigationStarted(&context);
  web_state_b->OnNavigationStarted(&context);
  web_state_c->OnNavigationStarted(&context);

  // The observer should only be notified for the active web state.
  ContextualPanelTabHelper* tab_helper_a =
      ContextualPanelTabHelper::FromWebState(web_state_a);
  ContextualPanelTabHelper* tab_helper_b =
      ContextualPanelTabHelper::FromWebState(web_state_b);
  ContextualPanelTabHelper* tab_helper_c =
      ContextualPanelTabHelper::FromWebState(web_state_c);
  EXPECT_TRUE(observer_.WasInvokedFor(tab_helper_c));
  EXPECT_FALSE(observer_.WasInvokedFor(tab_helper_a));
  EXPECT_FALSE(observer_.WasInvokedFor(tab_helper_b));
}

TEST_F(ActiveContextualPanelTabHelperObservationForwarderTest,
       TestReplaceActiveWebState) {
  // Insert two webstates into the list and mark the second one active.
  web::FakeWebState* web_state_a = AddWebStateToList(true);
  web::FakeWebState* web_state_b = AddWebStateToList(true);
  ASSERT_EQ(web_state_b, web_state_list_.GetActiveWebState());

  // Replace the active web state.  Send notifications and verify the result.
  auto replacement_web_state = std::make_unique<web::FakeWebState>();

  web::FakeWebState* web_state_c = replacement_web_state.get();
  std::map<ContextualPanelItemType, raw_ptr<ContextualPanelModel>> models;
  ContextualPanelTabHelper::CreateForWebState(web_state_c, models);
  std::unique_ptr<web::WebState> detached_web_state =
      web_state_list_.ReplaceWebStateAt(
          web_state_list_.GetIndexOfWebState(web_state_b),
          std::move(replacement_web_state));

  web::FakeNavigationContext context;
  web_state_a->OnNavigationStarted(&context);
  web_state_b->OnNavigationStarted(&context);
  web_state_c->OnNavigationStarted(&context);

  // The observer should only be notified for the new active web state C.
  ContextualPanelTabHelper* tab_helper_a =
      ContextualPanelTabHelper::FromWebState(web_state_a);
  ContextualPanelTabHelper* tab_helper_b =
      ContextualPanelTabHelper::FromWebState(web_state_b);
  ContextualPanelTabHelper* tab_helper_c =
      ContextualPanelTabHelper::FromWebState(web_state_c);
  EXPECT_TRUE(observer_.WasInvokedFor(tab_helper_c));
  EXPECT_FALSE(observer_.WasInvokedFor(tab_helper_a));
  EXPECT_FALSE(observer_.WasInvokedFor(tab_helper_b));
}

TEST_F(ActiveContextualPanelTabHelperObservationForwarderTest,
       TestChangeActiveWebState) {
  // Insert two webstates into the list and mark the second one active.
  web::FakeWebState* web_state_a = AddWebStateToList(true);
  web::FakeWebState* web_state_b = AddWebStateToList(true);
  ASSERT_EQ(web_state_b, web_state_list_.GetActiveWebState());

  // Make web state A active and send notifications.
  web_state_list_.ActivateWebStateAt(
      web_state_list_.GetIndexOfWebState(web_state_a));

  web::FakeNavigationContext context;
  web_state_a->OnNavigationStarted(&context);
  web_state_b->OnNavigationStarted(&context);

  // The observer should only be notified for the active web state A.
  ContextualPanelTabHelper* tab_helper_a =
      ContextualPanelTabHelper::FromWebState(web_state_a);
  ContextualPanelTabHelper* tab_helper_b =
      ContextualPanelTabHelper::FromWebState(web_state_b);
  EXPECT_TRUE(observer_.WasInvokedFor(tab_helper_a));
  EXPECT_FALSE(observer_.WasInvokedFor(tab_helper_b));

  // Make web state B active and send notifications.
  observer_.Reset();
  web_state_list_.ActivateWebStateAt(
      web_state_list_.GetIndexOfWebState(web_state_b));
  web_state_a->OnNavigationStarted(&context);
  web_state_b->OnNavigationStarted(&context);

  // The observer should only be notified for the active web state B.
  EXPECT_TRUE(observer_.WasInvokedFor(tab_helper_b));
  EXPECT_FALSE(observer_.WasInvokedFor(tab_helper_a));
}

TEST_F(ActiveContextualPanelTabHelperObservationForwarderTest,
       TestNonEmptyInitialWebStateList) {
  // Insert two webstates into the list.
  web::FakeWebState* web_state_a = AddWebStateToList(true);
  web::FakeWebState* web_state_b = AddWebStateToList(true);
  ASSERT_EQ(web_state_b, web_state_list_.GetActiveWebState());

  // Recreate the multi observer to simulate creation with an already-populated
  // WebStateList.
  forwarder_.reset();
  forwarder_ =
      std::make_unique<ActiveContextualPanelTabHelperObservationForwarder>(
          &web_state_list_, &observer_);

  // Send notifications and verify the result.
  web::FakeNavigationContext context;
  web_state_a->OnNavigationStarted(&context);
  web_state_b->OnNavigationStarted(&context);

  // The observer should only be notified for the active web state B.
  ContextualPanelTabHelper* tab_helper_a =
      ContextualPanelTabHelper::FromWebState(web_state_a);
  ContextualPanelTabHelper* tab_helper_b =
      ContextualPanelTabHelper::FromWebState(web_state_b);
  EXPECT_TRUE(observer_.WasInvokedFor(tab_helper_b));
  EXPECT_FALSE(observer_.WasInvokedFor(tab_helper_a));
}