blob: e392186045f991260495d3c620505bf1e70509ce [file] [log] [blame]
// Copyright 2023 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_model.h"
#include <optional>
#include "ash/birch/birch_item.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h"
#include "ash/public/cpp/test/test_image_downloader.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash {
namespace {
class StubBirchClient : public BirchClient {
public:
StubBirchClient() = default;
~StubBirchClient() override = default;
// BirchClient:
void RequestBirchDataFetch() override {}
};
class TestModelConsumer {
public:
void OnItemsReady(const std::string& id) {
items_ready_responses_.push_back(id);
}
const std::vector<std::string> items_ready_responses() const {
return items_ready_responses_;
}
private:
std::vector<std::string> items_ready_responses_;
};
} // namespace
class BirchModelTest : public AshTestBase {
public:
BirchModelTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
feature_list_.InitWithFeatures(
{features::kForestFeature, features::kBirchWeather}, {});
}
void SetUp() override {
switches::SetIgnoreForestSecretKeyForTest(true);
AshTestBase::SetUp();
// Inject no-op, stub weather provider to prevent real implementation from
// returning empty weather info.
Shell::Get()->birch_model()->OverrideWeatherProviderForTest(
std::make_unique<StubBirchClient>());
Shell::Get()->birch_model()->SetClient(&stub_birch_client_);
}
void TearDown() override {
Shell::Get()->birch_model()->SetClient(nullptr);
AshTestBase::TearDown();
switches::SetIgnoreForestSecretKeyForTest(false);
}
protected:
base::test::ScopedFeatureList feature_list_;
StubBirchClient stub_birch_client_;
};
class BirchModelWithoutWeatherTest : public AshTestBase {
public:
BirchModelWithoutWeatherTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
void SetUp() override {
switches::SetIgnoreForestSecretKeyForTest(true);
AshTestBase::SetUp();
Shell::Get()->birch_model()->SetClient(&stub_birch_client_);
}
void TearDown() override {
Shell::Get()->birch_model()->SetClient(nullptr);
AshTestBase::TearDown();
switches::SetIgnoreForestSecretKeyForTest(false);
}
protected:
base::test::ScopedFeatureList feature_list_{features::kForestFeature};
StubBirchClient stub_birch_client_;
};
// Test that requesting data and adding all fresh items to the model will run
// the callback.
TEST_F(BirchModelTest, AddItemNotifiesCallback) {
BirchModel* model = Shell::Get()->birch_model();
TestModelConsumer consumer;
EXPECT_TRUE(model);
// Setting items in the model does not notify when no request has occurred.
model->SetCalendarItems(std::vector<BirchCalendarItem>());
model->SetRecentTabItems(std::vector<BirchTabItem>());
model->SetFileSuggestItems(std::vector<BirchFileItem>());
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
// Make a data fetch request and set fresh tab data.
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"0"));
model->SetRecentTabItems(std::vector<BirchTabItem>());
// Consumer is not notified until all data sources have responded.
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
std::vector<BirchFileItem> file_item_list;
file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt);
model->SetFileSuggestItems(std::move(file_item_list));
model->SetWeatherItems({});
model->SetCalendarItems({});
// Adding file items sets all data as fresh, notifying consumers.
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0"));
// Setting the file suggest items should not trigger items ready again, since
// no data fetch was requested.
file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt);
file_item_list.emplace_back(base::FilePath("test path 2"), std::nullopt);
model->SetFileSuggestItems(std::move(file_item_list));
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0"));
// Request another data fetch and expect the consumer to be notified once
// items are set again.
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"1"));
model->SetRecentTabItems(std::vector<BirchTabItem>());
model->SetFileSuggestItems(std::move(file_item_list));
model->SetWeatherItems({});
model->SetCalendarItems({});
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0", "1"));
}
// TODO(https://crbug.com/324963992): Fix `BirchModel*Test.DataFetchTimeout`
// for debug builds.
#if defined(NDEBUG)
#define MAYBE_DataFetchTimeout DataFetchTimeout
#else
#define MAYBE_DataFetchTimeout DISABLED_DataFetchTimeout
#endif
// Test that consumer is notified when waiting a set amount of time after
// requesting birch data.
TEST_F(BirchModelTest, MAYBE_DataFetchTimeout) {
BirchModel* model = Shell::Get()->birch_model();
TestModelConsumer consumer;
EXPECT_TRUE(model);
// Passing time and setting data before requesting a birch data fetch will
// not notify consumer.
task_environment()->FastForwardBy(base::Milliseconds(1000));
std::vector<BirchFileItem> file_item_list;
file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt);
model->SetFileSuggestItems(std::move(file_item_list));
model->SetRecentTabItems(std::vector<BirchTabItem>());
std::vector<BirchWeatherItem> weather_items;
weather_items.emplace_back(u"desc", u"temp", ui::ImageModel());
model->SetWeatherItems(std::move(weather_items));
model->SetCalendarItems({});
EXPECT_TRUE(model->IsDataFresh());
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"0"));
EXPECT_FALSE(model->IsDataFresh());
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
// Test that passing a short amount of time and setting some data does not
// notify that items are ready.
task_environment()->FastForwardBy(base::Milliseconds(500));
std::vector<BirchTabItem> tab_item_list;
tab_item_list.emplace_back(u"tab title", GURL("example.com"),
base::Time::Now(), GURL("example.com/favicon_url"),
"session_name");
model->SetRecentTabItems(tab_item_list);
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
// Test that passing enough time notifies that items are ready.
task_environment()->FastForwardBy(base::Milliseconds(500));
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0"));
std::vector<std::unique_ptr<BirchItem>> all_items = model->GetAllItems();
EXPECT_EQ(all_items.size(), 3u);
EXPECT_STREQ(all_items[0]->GetItemType(), BirchTabItem::kItemType);
EXPECT_STREQ(all_items[1]->GetItemType(), BirchFileItem::kItemType);
EXPECT_STREQ(all_items[2]->GetItemType(), BirchWeatherItem::kItemType);
EXPECT_FALSE(model->IsDataFresh());
}
TEST_F(BirchModelWithoutWeatherTest, MAYBE_DataFetchTimeout) {
BirchModel* model = Shell::Get()->birch_model();
TestModelConsumer consumer;
EXPECT_TRUE(model);
std::vector<BirchFileItem> file_item_list;
file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt);
// Passing time and setting data before requesting a birch data fetch will
// not notify consumer.
task_environment()->FastForwardBy(base::Milliseconds(1000));
model->SetRecentTabItems(std::vector<BirchTabItem>());
model->SetFileSuggestItems(std::move(file_item_list));
model->SetCalendarItems({});
EXPECT_TRUE(model->IsDataFresh());
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"0"));
EXPECT_FALSE(model->IsDataFresh());
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
// Test that passing a short amount of time and setting some data does not
// notify that items are ready.
task_environment()->FastForwardBy(base::Milliseconds(500));
std::vector<BirchTabItem> tab_item_list;
tab_item_list.emplace_back(u"tab title", GURL("example.com"),
base::Time::Now(), GURL("example.com/favicon_url"),
"session_name");
model->SetRecentTabItems(tab_item_list);
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
// Test that passing enough time notifies that items are ready.
task_environment()->FastForwardBy(base::Milliseconds(500));
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0"));
std::vector<std::unique_ptr<BirchItem>> all_items = model->GetAllItems();
EXPECT_EQ(all_items.size(), 2u);
EXPECT_STREQ(all_items[0]->GetItemType(), BirchTabItem::kItemType);
EXPECT_STREQ(all_items[1]->GetItemType(), BirchFileItem::kItemType);
EXPECT_FALSE(model->IsDataFresh());
}
TEST_F(BirchModelWithoutWeatherTest, AddItemNotifiesCallback) {
BirchModel* model = Shell::Get()->birch_model();
TestModelConsumer consumer;
EXPECT_TRUE(model);
// Setting items in the model does not notify when no request has occurred.
model->SetRecentTabItems(std::vector<BirchTabItem>());
model->SetFileSuggestItems(std::vector<BirchFileItem>());
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
// Make a data fetch request and set fresh tab data.
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"0"));
model->SetRecentTabItems(std::vector<BirchTabItem>());
// Consumer is not notified until all data sources have responded.
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
std::vector<BirchFileItem> file_item_list;
file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt);
model->SetFileSuggestItems(std::move(file_item_list));
model->SetWeatherItems({});
model->SetCalendarItems({});
// Adding file items sets all data as fresh, notifying consumers.
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0"));
// Setting the file suggest items should not trigger items ready again, since
// no data fetch was requested.
file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt);
file_item_list.emplace_back(base::FilePath("test path 2"), std::nullopt);
model->SetFileSuggestItems(std::move(file_item_list));
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0"));
// Request another data fetch and expect the consumer to be notified once
// items are set again.
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"1"));
model->SetRecentTabItems(std::vector<BirchTabItem>());
model->SetFileSuggestItems(std::move(file_item_list));
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0", "1"));
}
TEST_F(BirchModelTest, MultipleRequestsHaveIndependentTimeouts) {
BirchModel* model = Shell::Get()->birch_model();
TestModelConsumer consumer;
EXPECT_TRUE(model);
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"0"));
task_environment()->FastForwardBy(base::Milliseconds(500));
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"1"));
task_environment()->FastForwardBy(base::Milliseconds(500));
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0"));
task_environment()->FastForwardBy(base::Milliseconds(500));
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0", "1"));
EXPECT_FALSE(model->IsDataFresh());
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"2"));
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0", "1"));
task_environment()->FastForwardBy(base::Milliseconds(1000));
EXPECT_THAT(consumer.items_ready_responses(),
testing::ElementsAre("0", "1", "2"));
EXPECT_FALSE(model->IsDataFresh());
}
TEST_F(BirchModelTest, ResponseAfterFirstTimeout) {
BirchModel* model = Shell::Get()->birch_model();
TestModelConsumer consumer;
EXPECT_TRUE(model);
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"0"));
task_environment()->FastForwardBy(base::Milliseconds(500));
EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty());
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"1"));
task_environment()->FastForwardBy(base::Milliseconds(500));
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0"));
task_environment()->FastForwardBy(base::Milliseconds(100));
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0"));
EXPECT_FALSE(model->IsDataFresh());
std::vector<BirchFileItem> file_item_list;
file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt);
model->SetFileSuggestItems(std::move(file_item_list));
std::vector<BirchWeatherItem> weather_item_list;
weather_item_list.emplace_back(u"cloudy", u"16 c", ui::ImageModel());
model->SetWeatherItems(std::move(weather_item_list));
std::vector<BirchTabItem> tab_item_list;
tab_item_list.emplace_back(u"tab", GURL("foo.bar"), base::Time(),
GURL("favicon"), "session");
model->SetRecentTabItems(std::move(tab_item_list));
std::vector<BirchCalendarItem> calendar_item_list;
calendar_item_list.emplace_back(u"Event 1", GURL(), base::Time(),
base::Time());
model->SetCalendarItems(std::move(calendar_item_list));
EXPECT_TRUE(model->IsDataFresh());
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0", "1"));
EXPECT_EQ(model->GetAllItems().size(), 4u);
model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady,
base::Unretained(&consumer),
/*id=*/"2"));
EXPECT_FALSE(model->IsDataFresh());
task_environment()->FastForwardBy(base::Milliseconds(100));
EXPECT_FALSE(model->IsDataFresh());
EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0", "1"));
model->SetFileSuggestItems({});
model->SetWeatherItems({});
model->SetRecentTabItems({});
model->SetCalendarItems({});
EXPECT_THAT(consumer.items_ready_responses(),
testing::ElementsAre("0", "1", "2"));
EXPECT_EQ(model->GetAllItems().size(), 0u);
EXPECT_TRUE(model->IsDataFresh());
}
} // namespace ash