chromium/ash/birch/birch_item_unittest.cc

// 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.

#include "ash/birch/birch_item.h"

#include <memory>
#include <utility>

#include "ash/birch/birch_icon_cache.h"
#include "ash/birch/birch_model.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/test/test_image_downloader.h"
#include "ash/public/cpp/test/test_new_window_delegate.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/time/calendar_unittest_utils.h"
#include "ash/test/ash_test_base.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_mock_clock_override.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "chromeos/ash/components/settings/scoped_timezone_settings.h"
#include "components/prefs/pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/models/image_model.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_manager.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/native_theme/native_theme.h"

namespace ash {
namespace {

PrefService* GetPrefService() {
  return Shell::Get()->session_controller()->GetPrimaryUserPrefService();
}

class TestNewWindowDelegateImpl : public TestNewWindowDelegate {
 public:
  // TestNewWindowDelegate:
  void OpenUrl(const GURL& url,
               OpenUrlFrom from,
               Disposition disposition) override {
    last_opened_url_ = url;
  }

  void OpenFile(const base::FilePath& file_path) override {
    last_opened_file_path_ = file_path;
  }

  GURL last_opened_url_;
  base::FilePath last_opened_file_path_;
};

class StubBirchClient : public BirchClient {
 public:
  StubBirchClient() = default;
  ~StubBirchClient() override = default;

  // BirchClient:
  BirchDataProvider* GetCalendarProvider() override { return nullptr; }
  BirchDataProvider* GetFileSuggestProvider() override { return nullptr; }
  BirchDataProvider* GetRecentTabsProvider() override { return nullptr; }
  BirchDataProvider* GetLastActiveProvider() override { return nullptr; }
  BirchDataProvider* GetMostVisitedProvider() override { return nullptr; }
  BirchDataProvider* GetSelfShareProvider() override { return nullptr; }
  BirchDataProvider* GetLostMediaProvider() override { return nullptr; }
  BirchDataProvider* GetReleaseNotesProvider() override { return nullptr; }
  void WaitForRefreshTokens(base::OnceClosure callback) override {}
  base::FilePath GetRemovedItemsFilePath() override { return base::FilePath(); }
  void RemoveFileItemFromLauncher(const base::FilePath& path) override {}

  void GetFaviconImage(
      const GURL& url,
      const bool is_page_url,
      base::OnceCallback<void(const ui::ImageModel&)> callback) override {
    did_get_favicon_image_ = true;
    std::move(callback).Run(ui::ImageModel());
  }

  bool did_get_favicon_image_ = false;
};

class BirchItemTest : public testing::Test {
 public:
  BirchItemTest()
      : ash_timezone_(u"America/Los_Angeles"),
        scoped_libc_timezone_("America/Los_Angeles") {
    auto new_window_delegate = std::make_unique<TestNewWindowDelegateImpl>();
    new_window_delegate_ = new_window_delegate.get();
    new_window_delegate_provider_ =
        std::make_unique<TestNewWindowDelegateProvider>(
            std::move(new_window_delegate));
    BirchItem::set_action_count_for_test(0);

    // The mock clock starts with a fixed but arbitrary time. Adjust the time
    // to make the test times more readable (this makes "now" 5 PM).
    mock_clock_override_.Advance(base::Minutes(53));
  }

  ~BirchItemTest() override { BirchItem::set_action_count_for_test(0); }

  std::unique_ptr<TestNewWindowDelegateProvider> new_window_delegate_provider_;
  raw_ptr<TestNewWindowDelegateImpl> new_window_delegate_ = nullptr;
  // Use an arbitrary but fixed "now" time for tests.
  base::ScopedMockClockOverride mock_clock_override_;

  // Ensure consistent timezones for testing.
  ash::system::ScopedTimezoneSettings ash_timezone_;
  calendar_test_utils::ScopedLibcTimeZone scoped_libc_timezone_;
};

TEST_F(BirchItemTest, RecordActionMetrics_Basics) {
  base::HistogramTester histograms;
  BirchWeatherItem item(u"item", 72.f, GURL("http://icon.com/"));
  item.set_ranking(5.f);
  item.RecordActionMetrics();
  histograms.ExpectBucketCount("Ash.Birch.Bar.Activate", true, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.Activate",
                               BirchItemType::kWeather, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.ActivatedRanking", 5, 1);
}

TEST_F(BirchItemTest, RecordActionMetrics_FirstSecondThird) {
  base::HistogramTester histograms;
  BirchWeatherItem item(u"item", 72.f, GURL("http://icon.com/"));

  // First action records in "ActivateFirst" metric.
  item.RecordActionMetrics();
  histograms.ExpectBucketCount("Ash.Birch.Chip.ActivateFirst",
                               BirchItemType::kWeather, 1);
  histograms.ExpectTotalCount("Ash.Birch.Chip.ActivateSecond", 0);
  histograms.ExpectTotalCount("Ash.Birch.Chip.ActivateThird", 0);

  // Second action records in "ActivateSecond" metric.
  item.RecordActionMetrics();
  histograms.ExpectBucketCount("Ash.Birch.Chip.ActivateFirst",
                               BirchItemType::kWeather, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.ActivateSecond",
                               BirchItemType::kWeather, 1);
  histograms.ExpectTotalCount("Ash.Birch.Chip.ActivateThird", 0);

  // Third action records in "ActivateThird" metric.
  item.RecordActionMetrics();
  histograms.ExpectBucketCount("Ash.Birch.Chip.ActivateFirst",
                               BirchItemType::kWeather, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.ActivateSecond",
                               BirchItemType::kWeather, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.ActivateThird",
                               BirchItemType::kWeather, 1);

  // Fourth action doesn't change the metrics.
  item.RecordActionMetrics();
  histograms.ExpectBucketCount("Ash.Birch.Chip.ActivateFirst",
                               BirchItemType::kWeather, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.ActivateSecond",
                               BirchItemType::kWeather, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.ActivateThird",
                               BirchItemType::kWeather, 1);
}

// When both conference URL and calendar URL are set, the conference URL is
// preferred.
TEST_F(BirchItemTest, Calendar_PerformAction_BothConferenceAndCalendar) {
  // Create an event that is happening now so the "Join" action is enabled.
  base::Time now = base::Time::Now();
  BirchCalendarItem item(u"item", /*start_time=*/now - base::Minutes(30),
                         /*end_time=*/now + base::Minutes(30),
                         /*calendar_url=*/GURL("http://calendar.com"),
                         /*conference_url=*/GURL("http://meet.com"),
                         /*event_id=*/"000",
                         /*all_day_event=*/false);
  item.PerformAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_,
            GURL("http://calendar.com/"));

  EXPECT_TRUE(item.addon_label());
  item.PerformAddonAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_, GURL("http://meet.com/"));
}

TEST_F(BirchItemTest, Calendar_PerformAction_Histograms) {
  base::HistogramTester histograms;
  BirchCalendarItem item(u"item", /*start_time=*/base::Time(),
                         /*end_time=*/base::Time(),
                         /*calendar_url=*/GURL("http://calendar.com"),
                         /*conference_url=*/GURL("http://meet.com"),
                         /*event_id=*/"000",
                         /*all_day_event=*/false);
  item.PerformAction();
  histograms.ExpectBucketCount("Ash.Birch.Bar.Activate", true, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.Activate",
                               BirchItemType::kCalendar, 1);

  item.PerformAddonAction();
  histograms.ExpectBucketCount("Ash.Birch.Bar.Activate", true, 2);
  histograms.ExpectBucketCount("Ash.Birch.Chip.Activate",
                               BirchItemType::kCalendar, 2);
}

// If only the calendar URL is set, it is opened.
TEST_F(BirchItemTest, Calendar_PerformAction_CalendarOnly) {
  BirchCalendarItem item(u"item", /*start_time=*/base::Time(),
                         /*end_time=*/base::Time(),
                         /*calendar_url=*/GURL("http://calendar.com"),
                         /*conference_url=*/GURL(),
                         /*event_id=*/"000",
                         /*all_day_event=*/false);
  item.PerformAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_,
            GURL("http://calendar.com/"));

  EXPECT_FALSE(item.addon_label());
  item.PerformAddonAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_,
            GURL("http://calendar.com/"));
}

// If neither the conference URL nor the calendar URL is set, nothing opens.
TEST_F(BirchItemTest, Calendar_PerformAction_NoURL) {
  BirchCalendarItem item(u"item", /*start_time=*/base::Time(),
                         /*end_time=*/base::Time(),
                         /*calendar_url=*/GURL(),
                         /*conference_url=*/GURL(),
                         /*event_id=*/"000",
                         /*all_day_event=*/false);
  item.PerformAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_, GURL());
}

TEST_F(BirchItemTest, Calendar_ShouldShowAddonAction) {
  base::Time now = base::Time::Now();

  // Create an event with a conference URL, but in the future.
  BirchCalendarItem item0(u"item0", /*start_time=*/now + base::Hours(1),
                          /*end_time=*/now + base::Hours(2),
                          /*calendar_url=*/GURL("http://calendar.com"),
                          /*conference_url=*/GURL("http://meet.com"),
                          /*event_id=*/"000",
                          /*all_day_event=*/false);

  // The meeting is in the future, so don't show the "Join" button.
  EXPECT_FALSE(item0.addon_label().has_value());

  // Create a meeting happening right now.
  BirchCalendarItem item1(u"item1",
                          /*start_time=*/now - base::Minutes(30),
                          /*end_time=*/now + base::Minutes(30),
                          /*calendar_url=*/GURL("http://calendar.com"),
                          /*conference_url=*/GURL("http://meet.com"),
                          /*event_id=*/"001",
                          /*all_day_event=*/false);

  // The meeting is happening now, so show the "Join" button.
  EXPECT_TRUE(item1.addon_label().has_value());

  // Create a meeting starting in the next few minutes.
  BirchCalendarItem item2(u"item2", /*start_time=*/now + base::Minutes(3),
                          /*end_time=*/now + base::Minutes(33),
                          /*calendar_url=*/GURL("http://calendar.com"),
                          /*conference_url=*/GURL("http://meet.com"),
                          /*event_id=*/"002",
                          /*all_day_event=*/false);

  // The meeting is very soon, so show the "Join" button.
  EXPECT_TRUE(item2.addon_label().has_value());
}

TEST_F(BirchItemTest, Calendar_Subtitle_Ongoing) {
  BirchCalendarItem item(u"item",
                         /*start_time=*/base::Time::Now() - base::Minutes(30),
                         /*end_time=*/base::Time::Now() + base::Minutes(30),
                         /*calendar_url=*/GURL("http://calendar.com"),
                         /*conference_url=*/GURL(),
                         /*event_id=*/"000",
                         /*all_day_event=*/false);
  EXPECT_EQ(item.subtitle(), u"Now · Ends 5:30 PM");
}

TEST_F(BirchItemTest, Calendar_Subtitle_Soon) {
  BirchCalendarItem item(u"item",
                         /*start_time=*/base::Time::Now() + base::Minutes(15),
                         /*end_time=*/base::Time::Now() + base::Hours(1),
                         /*calendar_url=*/GURL("http://calendar.com"),
                         /*conference_url=*/GURL(),
                         /*event_id=*/"000",
                         /*all_day_event=*/false);
  EXPECT_EQ(item.subtitle(), u"In 15 mins · 5:15 PM - 6:00 PM");
}

TEST_F(BirchItemTest, Calendar_Subtitle_NotSoon) {
  BirchCalendarItem item(u"item",
                         /*start_time=*/base::Time::Now() + base::Hours(1),
                         /*end_time=*/base::Time::Now() + base::Hours(2),
                         /*calendar_url=*/GURL("http://calendar.com"),
                         /*conference_url=*/GURL(),
                         /*event_id=*/"000",
                         /*all_day_event=*/false);
  EXPECT_EQ(item.subtitle(), u"6:00 PM - 7:00 PM");
}

TEST_F(BirchItemTest, Calendar_Subtitle_Tomorrow) {
  base::Time next_midnight = base::Time::Now().LocalMidnight() + base::Days(1);
  BirchCalendarItem item(u"item",
                         /*start_time=*/next_midnight + base::Hours(1),
                         /*end_time=*/next_midnight + base::Hours(2),
                         /*calendar_url=*/GURL("http://calendar.com"),
                         /*conference_url=*/GURL(),
                         /*event_id=*/"000",
                         /*all_day_event=*/false);
  EXPECT_EQ(item.subtitle(), u"Tomorrow · 1:00 AM - 2:00 AM");
}

TEST_F(BirchItemTest, Calendar_Subtitle_AllDay) {
  base::Time next_midnight = base::Time::Now().LocalMidnight() + base::Days(1);
  BirchCalendarItem item(u"item",
                         /*start_time=*/next_midnight - base::Days(1),
                         /*end_time=*/next_midnight,
                         /*calendar_url=*/GURL("http://calendar.com"),
                         /*conference_url=*/GURL(),
                         /*event_id=*/"000",
                         /*all_day_event=*/true);
  EXPECT_EQ(item.subtitle(), u"All Day");
}

TEST_F(BirchItemTest, Attachment_PerformAction_ValidUrl) {
  BirchAttachmentItem item(u"item",
                           /*file_url=*/GURL("http://file.com/"),
                           /*icon_url=*/GURL("http://attachment.icon"),
                           /*start_time=*/base::Time(),
                           /*end_time=*/base::Time(),
                           /*file_id=*/"");
  item.PerformAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_, GURL("http://file.com/"));
}

TEST_F(BirchItemTest, Attachment_PerformAction_Histograms) {
  base::HistogramTester histograms;
  BirchAttachmentItem item(u"item",
                           /*file_url=*/GURL("http://file.com/"),
                           /*icon_url=*/GURL("http://attachment.icon"),
                           /*start_time=*/base::Time(),
                           /*end_time=*/base::Time(),
                           /*file_id=*/"");
  item.PerformAction();
  histograms.ExpectBucketCount("Ash.Birch.Bar.Activate", true, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.Activate",
                               BirchItemType::kAttachment, 1);
}

TEST_F(BirchItemTest, Attachment_PerformAction_EmptyUrl) {
  BirchAttachmentItem item(u"item",
                           /*file_url=*/GURL(),
                           /*icon_url=*/GURL("http://attachment.icon"),
                           /*start_time=*/base::Time(),
                           /*end_time=*/base::Time(),
                           /*file_id=*/"");
  item.PerformAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_, GURL());
}

TEST_F(BirchItemTest, Attachment_Subtitle_Now) {
  base::Time now = base::Time::Now();
  BirchAttachmentItem item(u"item",
                           /*file_url=*/GURL("http://file.com/"),
                           /*icon_url=*/GURL("http://attachment.icon"),
                           /*start_time=*/now - base::Minutes(30),
                           /*end_time=*/now + base::Minutes(30),
                           /*file_id=*/"");
  EXPECT_EQ(item.subtitle(), u"From event happening now");
}

TEST_F(BirchItemTest, Attachment_Subtitle_Upcoming) {
  base::Time now = base::Time::Now();
  BirchAttachmentItem item(u"item",
                           /*file_url=*/GURL("http://file.com/"),
                           /*icon_url=*/GURL("http://attachment.icon"),
                           /*start_time=*/now + base::Hours(1),
                           /*end_time=*/now + base::Hours(2),
                           /*file_id=*/"");
  EXPECT_EQ(item.subtitle(), u"From upcoming calendar event");
}

TEST_F(BirchItemTest, File_TitleDoesNotShowFileExtension) {
  BirchFileItem item(base::FilePath("/path/to/file.gdoc"), std::nullopt,
                     u"suggested", base::Time(), "id_1", "icon_url");
  // The title does not contain the ".gdoc" extension.
  EXPECT_EQ(u"file", item.title());
}

TEST_F(BirchItemTest, File_Title) {
  BirchFileItem item(base::FilePath("/path/to/file.gdoc"), "file_title",
                     u"suggested", base::Time(), "id_1", "icon_url");
  // When set, the title will take precedence over the file path.
  EXPECT_EQ(u"file_title", item.title());
}

TEST_F(BirchItemTest, File_PerformAction) {
  BirchFileItem item(base::FilePath("file_path"), "title", u"suggested",
                     base::Time(), "id_1", "icon_url");
  EXPECT_EQ(u"title", item.title());
  EXPECT_EQ(u"suggested", item.subtitle());
  EXPECT_EQ("id_1", item.file_id());

  item.PerformAction();
  EXPECT_EQ(new_window_delegate_->last_opened_file_path_,
            base::FilePath("file_path"));
}

TEST_F(BirchItemTest, File_PerformAction_Histograms) {
  base::HistogramTester histograms;
  BirchFileItem item(base::FilePath("file_path"), "title", u"suggested",
                     base::Time(), "id_1", "icon_url");
  item.PerformAction();
  histograms.ExpectBucketCount("Ash.Birch.Bar.Activate", true, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.Activate", BirchItemType::kFile,
                               1);
}

TEST_F(BirchItemTest, Weather_PerformAction) {
  BirchWeatherItem item(u"item", 72.f, GURL("http://icon.com/"));
  item.PerformAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_,
            GURL("https://google.com/search?q=weather"));
}

TEST_F(BirchItemTest, Weather_PerformAction_Histograms) {
  base::HistogramTester histograms;
  BirchWeatherItem item(u"item", 72.f, GURL("http://icon.com/"));
  item.PerformAction();
  histograms.ExpectBucketCount("Ash.Birch.Bar.Activate", true, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.Activate",
                               BirchItemType::kWeather, 1);
}

// Weather item subtitles require an ash::Shell for the pref service.
using BirchWeatherItemTest = AshTestBase;

TEST_F(BirchWeatherItemTest, AddonLabelInFahrenheit) {
  GetPrefService()->SetBoolean(prefs::kBirchUseCelsius, false);
  BirchWeatherItem item(u"item", 72.f, GURL("http://icon.com/"));
  EXPECT_EQ(item.addon_label(), u"72");
}

TEST_F(BirchWeatherItemTest, AddonLabelInCelsius) {
  GetPrefService()->SetBoolean(prefs::kBirchUseCelsius, true);
  BirchWeatherItem item(u"item", 72.f, GURL("http://icon.com/"));
  EXPECT_EQ(item.addon_label(), u"22");
}

TEST_F(BirchItemTest, Tab_Subtitle_Recent) {
  BirchTabItem item(u"item", /*url=*/GURL("http://example.com/"),
                    /*timestamp=*/base::Time::Now() - base::Minutes(5),
                    /*favicon_url=*/GURL(), /*session_name=*/"Chromebook",
                    /*form_factor=*/BirchTabItem::DeviceFormFactor::kDesktop);
  EXPECT_EQ(item.subtitle(), u"Within 1 hr · From Chromebook");
}

TEST_F(BirchItemTest, Tab_Subtitle_OneHour) {
  BirchTabItem item(u"item", /*url=*/GURL("http://example.com/"),
                    /*timestamp=*/base::Time::Now() - base::Minutes(65),
                    /*favicon_url=*/GURL(), /*session_name=*/"Chromebook",
                    /*form_factor=*/BirchTabItem::DeviceFormFactor::kDesktop);
  EXPECT_EQ(item.subtitle(), u"1 hr ago · From Chromebook");
}

TEST_F(BirchItemTest, Tab_Subtitle_TwoHours) {
  BirchTabItem item(u"item", /*url=*/GURL("http://example.com/"),
                    /*timestamp=*/base::Time::Now() - base::Minutes(125),
                    /*favicon_url=*/GURL(), /*session_name=*/"Chromebook",
                    /*form_factor=*/BirchTabItem::DeviceFormFactor::kDesktop);
  EXPECT_EQ(item.subtitle(), u"2 hr ago · From Chromebook");
}

TEST_F(BirchItemTest, Tab_Subtitle_Yesterday) {
  BirchTabItem item(
      u"item", /*url=*/GURL("http://example.com/"),
      /*timestamp=*/base::Time::Now().LocalMidnight() - base::Minutes(5),
      /*favicon_url=*/GURL(), /*session_name=*/"Chromebook",
      /*form_factor=*/BirchTabItem::DeviceFormFactor::kDesktop);
  EXPECT_EQ(item.subtitle(), u"Yesterday · From Chromebook");
}

TEST_F(BirchItemTest, Tab_PerformAction_ValidUrl) {
  BirchTabItem item(u"item", /*url=*/GURL("http://example.com/"),
                    /*timestamp=*/base::Time(),
                    /*favicon_url=*/GURL(), /*session_name=*/"",
                    /*form_factor=*/BirchTabItem::DeviceFormFactor::kDesktop);
  item.PerformAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_,
            GURL("http://example.com/"));
}

TEST_F(BirchItemTest, Tab_PerformAction_EmptyUrl) {
  BirchTabItem item(u"item", /*url=*/GURL(),
                    /*timestamp=*/base::Time(),
                    /*favicon_url=*/GURL(), /*session_name=*/"",
                    /*form_factor=*/BirchTabItem::DeviceFormFactor::kDesktop);
  item.PerformAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_, GURL());
}

TEST_F(BirchItemTest, Tab_PerformAction_Histograms) {
  base::HistogramTester histograms;
  BirchTabItem item(u"item", /*url=*/GURL("http://example.com/"),
                    /*timestamp=*/base::Time(),
                    /*favicon_url=*/GURL(), /*session_name=*/"",
                    /*form_factor=*/BirchTabItem::DeviceFormFactor::kDesktop);
  item.PerformAction();
  histograms.ExpectBucketCount("Ash.Birch.Bar.Activate", true, 1);
  histograms.ExpectBucketCount("Ash.Birch.Chip.Activate", BirchItemType::kTab,
                               1);
}

TEST_F(BirchItemTest, LastActive_Subtitle_TwoDaysAgo) {
  BirchLastActiveItem item(u"item", GURL("http://example.com/"),
                           base::Time::Now() - base::Days(2));
  EXPECT_EQ(item.subtitle(), u"2 days ago · Continue browsing");
}

TEST_F(BirchItemTest, LastActive_Subtitle_Yesterday) {
  BirchLastActiveItem item(u"item", GURL("http://example.com/"),
                           base::Time::Now() - base::Days(1));
  EXPECT_EQ(item.subtitle(), u"Yesterday · Continue browsing");
}

TEST_F(BirchItemTest, LastActive_Subtitle_OneHourAgo) {
  BirchLastActiveItem item(u"item", GURL("http://example.com/"),
                           base::Time::Now() - base::Hours(1));
  EXPECT_EQ(item.subtitle(), u"1 hr ago · Continue browsing");
}

TEST_F(BirchItemTest, LastActive_PerformAction) {
  BirchLastActiveItem item(u"item", GURL("http://example.com/"), base::Time());
  item.PerformAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_,
            GURL("http://example.com/"));
}

TEST_F(BirchItemTest, SelfShare_PerformAction) {
  base::MockCallback<base::RepeatingClosure> activation_callback;
  BirchSelfShareItem item(
      /*guid=*/u"self share guid", /*title*/ u"self share tab",
      /*url=*/GURL("https://www.example.com/"),
      /*shared_time=*/base::Time(), /*device_name=*/u"my device",
      /*secondary_icon_type=*/SecondaryIconType::kTabFromDesktop,
      /*activation_callback=*/activation_callback.Get());
  EXPECT_CALL(activation_callback, Run).Times(1);
  item.PerformAction();
  EXPECT_EQ(new_window_delegate_->last_opened_url_,
            GURL("https://www.example.com/"));
}

////////////////////////////////////////////////////////////////////////////////

// The icon downloader requires ash::Shell, so use AshTestBase.
class BirchItemIconTest : public AshTestBase {
 public:
  BirchItemIconTest() {
    feature_list_.InitAndEnableFeature(features::kForestFeature);
  }

  void SetUp() override {
    AshTestBase::SetUp();
    Shell::Get()->birch_model()->SetClientAndInit(&stub_birch_client_);
  }

  void TearDown() override {
    Shell::Get()->birch_model()->SetClientAndInit(nullptr);
    AshTestBase::TearDown();
  }

  StubBirchClient stub_birch_client_;
  TestImageDownloader image_downloader_;
  base::test::ScopedFeatureList feature_list_;
};

TEST_F(BirchItemIconTest, Calendar_LoadIcon) {
  BirchCalendarItem item(u"item", /*start_time=*/base::Time(),
                         /*end_time=*/base::Time(),
                         /*calendar_url=*/GURL("http://calendar.com"),
                         /*conference_url=*/GURL("http://meet.com"),
                         /*event_id=*/"000",
                         /*all_day_event=*/false);

  item.LoadIcon(base::BindOnce(
      [](const ui::ImageModel& icon, SecondaryIconType secondary_icon_type) {
        EXPECT_FALSE(icon.IsEmpty());
        EXPECT_EQ(secondary_icon_type, SecondaryIconType::kNoIcon);
      }));
}

TEST_F(BirchItemIconTest, Attachment_LoadIcon) {
  BirchAttachmentItem item(u"item",
                           /*file_url=*/GURL("http://file.com/"),
                           /*icon_url=*/GURL("http://attachment.icon"),
                           /*start_time=*/base::Time(),
                           /*end_time=*/base::Time(),
                           /*file_id=*/"");

  base::test::TestFuture<const ui::ImageModel&, SecondaryIconType> future;
  item.LoadIcon(future.GetCallback());
  // The icon is not empty.
  EXPECT_FALSE(future.Get<0>().IsEmpty());
  // Secondary icon is of type no icon.
  EXPECT_EQ(future.Get<1>(), SecondaryIconType::kNoIcon);

  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
  EXPECT_EQ(icon_cache->size_for_test(), 1u);
  EXPECT_FALSE(icon_cache->Get("http://attachment.icon/").isNull());
}

TEST_F(BirchItemIconTest, Attachment_LoadIcon_InvalidUrl) {
  BirchAttachmentItem item(u"item",
                           /*file_url=*/GURL("http://file.com/"),
                           /*icon_url=*/GURL("invalid-url"),
                           /*start_time=*/base::Time(),
                           /*end_time=*/base::Time(),
                           /*file_id=*/"");

  base::test::TestFuture<const ui::ImageModel&, SecondaryIconType> future;
  item.LoadIcon(future.GetCallback());
  // Secondary icon is of type no icon.
  EXPECT_EQ(future.Get<1>(), SecondaryIconType::kNoIcon);

  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
  EXPECT_EQ(icon_cache->size_for_test(), 0u);
}

TEST_F(BirchItemIconTest, Tab_LoadIcon) {
  BirchTabItem item(u"item", /*url=*/GURL("http://example.com/"),
                    /*timestamp=*/base::Time(),
                    /*favicon_url=*/GURL("http://icon.com/"),
                    /*session_name=*/"",
                    /*form_factor=*/BirchTabItem::DeviceFormFactor::kDesktop);
  base::test::TestFuture<const ui::ImageModel&, SecondaryIconType> future;
  item.LoadIcon(future.GetCallback());
  // The favicon service was queried.
  EXPECT_TRUE(stub_birch_client_.did_get_favicon_image_);
  // The icon is not empty.
  EXPECT_FALSE(future.Get<0>().IsEmpty());
  // Secondary icon is of type no icon.
  EXPECT_EQ(future.Get<1>(), SecondaryIconType::kTabFromDesktop);

  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
  EXPECT_EQ(icon_cache->size_for_test(), 1u);
  EXPECT_FALSE(icon_cache->Get("http://icon.com/").isNull());
}

TEST_F(BirchItemIconTest, Tab_LoadIcon_InvalidUrl) {
  BirchTabItem item(u"item", /*url=*/GURL("http://example.com/"),
                    /*timestamp=*/base::Time(),
                    /*favicon_url=*/GURL("invalid-url"),
                    /*session_name=*/"",
                    /*form_factor=*/BirchTabItem::DeviceFormFactor::kDesktop);
  base::test::TestFuture<const ui::ImageModel&, SecondaryIconType> future;
  item.LoadIcon(future.GetCallback());
  // Secondary icon is of type no icon.
  EXPECT_EQ(future.Get<1>(), SecondaryIconType::kTabFromDesktop);

  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
  EXPECT_EQ(icon_cache->size_for_test(), 0u);
}

TEST_F(BirchItemIconTest, Weather_LoadIcon) {
  BirchWeatherItem item(u"item", 72.f, GURL("http://icon.com/"));

  item.LoadIcon(base::BindOnce(
      [](const ui::ImageModel& icon, SecondaryIconType secondary_icon_type) {
        EXPECT_FALSE(icon.IsEmpty());
        EXPECT_EQ(secondary_icon_type, SecondaryIconType::kNoIcon);
      }));
}

TEST_F(BirchItemIconTest, Weather_LoadIcon_NoIcon) {
  BirchWeatherItem item(u"Sunny", 72.f, GURL());

  item.LoadIcon(base::BindOnce(
      [](const ui::ImageModel& icon, SecondaryIconType secondary_icon_type) {
        // In the case where an invalid icon_url is provided, there should still
        // be a valid backup_icon.
        EXPECT_FALSE(icon.IsEmpty());
        EXPECT_EQ(secondary_icon_type, SecondaryIconType::kNoIcon);
      }));
}

TEST_F(BirchItemIconTest, File_LoadIcon) {
  const std::string icon_url =
      "https://drive-thirdparty.googleusercontent.com/32/type/application/"
      "vnd.google-apps.document";

  BirchFileItem item(base::FilePath("/path/to/file.gdoc"), "title",
                     u"suggested", base::Time(), "id_1", icon_url);

  base::test::TestFuture<const ui::ImageModel&, SecondaryIconType> future;
  item.LoadIcon(future.GetCallback());
  // The icon is not empty.
  EXPECT_FALSE(future.Get<0>().IsEmpty());
  // Secondary icon is of type no icon.
  EXPECT_EQ(future.Get<1>(), SecondaryIconType::kNoIcon);

  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
  EXPECT_EQ(icon_cache->size_for_test(), 1u);
  EXPECT_FALSE(icon_cache->Get(icon_url).isNull());
}

TEST_F(BirchItemIconTest, SelfShare_LoadIcon) {
  const GURL page_url = GURL("https://www.example.com/");
  BirchSelfShareItem item(
      u"self share guid", u"self share tab", page_url, base::Time(),
      u"my device", SecondaryIconType::kTabFromDesktop, base::DoNothing());
  base::test::TestFuture<const ui::ImageModel&, SecondaryIconType> future;
  item.LoadIcon(future.GetCallback());
  // The favicon service was queried.
  EXPECT_TRUE(stub_birch_client_.did_get_favicon_image_);
  // The icon is not empty.
  EXPECT_FALSE(future.Get<0>().IsEmpty());
  // Secondary icon is of type `kTabFromDesktop`.
  EXPECT_EQ(future.Get<1>(), SecondaryIconType::kTabFromDesktop);

  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
  EXPECT_EQ(icon_cache->size_for_test(), 1u);
  EXPECT_FALSE(icon_cache->Get(page_url.spec()).isNull());
}

TEST_F(BirchItemTest, LostMedia_VideoConference_Subtitle) {
  BirchLostMediaItem item(GURL(), u"test_title", std::nullopt,
                          SecondaryIconType::kLostMediaVideoConference,
                          base::DoNothing());
  EXPECT_EQ(item.subtitle(), u"Ongoing · Switch to tab");
}

TEST_F(BirchItemTest, LostMedia_MediaTab_Subtitle) {
  BirchLostMediaItem item(GURL(), u"test_title", std::nullopt,
                          SecondaryIconType::kLostMediaVideo,
                          base::DoNothing());
  EXPECT_EQ(item.subtitle(), u"Playing · Switch to tab");
}

TEST_F(BirchItemIconTest, LostMedia_LoadIcon) {
  const GURL page_url = GURL("https://www.example.com/");
  BirchLostMediaItem item(page_url, u"test_title", std::nullopt,
                          SecondaryIconType::kLostMediaVideoConference,
                          base::DoNothing());
  base::test::TestFuture<const ui::ImageModel&, SecondaryIconType> future;
  item.LoadIcon(future.GetCallback());
  // The favicon service was queried.
  EXPECT_TRUE(stub_birch_client_.did_get_favicon_image_);
  // The icon is not empty.
  EXPECT_FALSE(future.Get<0>().IsEmpty());
  // Secondary icon is of type `kLostMediaVideoConference`.
  EXPECT_EQ(future.Get<1>(), SecondaryIconType::kLostMediaVideoConference);

  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
  EXPECT_EQ(icon_cache->size_for_test(), 1u);
  EXPECT_FALSE(icon_cache->Get(page_url.spec()).isNull());
}

TEST_F(BirchItemIconTest, LastActive_LoadIcon) {
  const GURL page_url = GURL("https://www.example.com/");
  BirchLastActiveItem item(u"item", page_url, base::Time());

  base::test::TestFuture<const ui::ImageModel&, SecondaryIconType> future;
  item.LoadIcon(future.GetCallback());

  // The favicon service was queried.
  EXPECT_TRUE(stub_birch_client_.did_get_favicon_image_);
  // The icon is not empty.
  EXPECT_FALSE(future.Get<0>().IsEmpty());
  // Secondary icon is of type `kNoIcon`.
  EXPECT_EQ(future.Get<1>(), SecondaryIconType::kNoIcon);

  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
  EXPECT_EQ(icon_cache->size_for_test(), 1u);
  EXPECT_FALSE(icon_cache->Get(page_url.spec()).isNull());
}

TEST_F(BirchItemIconTest, MostVisited_LoadIcon) {
  const GURL page_url = GURL("https://www.example.com/");
  BirchMostVisitedItem item(u"item", page_url);

  base::test::TestFuture<const ui::ImageModel&, SecondaryIconType> future;
  item.LoadIcon(future.GetCallback());

  // The favicon service was queried.
  EXPECT_TRUE(stub_birch_client_.did_get_favicon_image_);
  // The icon is not empty.
  EXPECT_FALSE(future.Get<0>().IsEmpty());
  // Secondary icon is of type `kNoIcon`.
  EXPECT_EQ(future.Get<1>(), SecondaryIconType::kNoIcon);

  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
  EXPECT_EQ(icon_cache->size_for_test(), 1u);
  EXPECT_FALSE(icon_cache->Get(page_url.spec()).isNull());
}

}  // namespace
}  // namespace ash