Commit d438f8da authored by Dan Harrington's avatar Dan Harrington Committed by Commit Bot

Add support for zero-state

- Added new ZeroStateSlice
- The UI now should receive a zero state slice if
  loading fails.

I've changed SurfaceInterface so that the initial state isn't provided
on a different function call. This makes things a bit easier in
FeedStream.


Bug: 1044139
Change-Id: Ia8c3ec15fa0460b0add78ee5ba28e21f8a643237
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2133231Reviewed-by: default avatarIan Wells <iwells@chromium.org>
Commit-Queue: Dan H <harringtond@chromium.org>
Cr-Commit-Position: refs/heads/master@{#755996}
parent 362e7756
...@@ -38,10 +38,25 @@ message StreamUpdate { ...@@ -38,10 +38,25 @@ message StreamUpdate {
// A horizontal slice of UI to be presented in the vertical-scrolling feed. // A horizontal slice of UI to be presented in the vertical-scrolling feed.
message Slice { message Slice {
oneof SliceData { XSurfaceSlice xsurface_slice = 1; } oneof SliceData {
XSurfaceSlice xsurface_slice = 1;
ZeroStateSlice zero_state_slice = 3;
}
string slice_id = 2; string slice_id = 2;
} }
// This slice is sent when no feed data can be loaded.
message ZeroStateSlice {
enum Type {
UNKNOWN = 0;
// A generic error that explains there are no cards available.
NO_CARDS_AVAILABLE = 1;
// An error indicating there were problems refreshing the feed.
CANT_REFRESH = 2;
};
Type type = 1;
}
message XSurfaceSlice { message XSurfaceSlice {
bytes xsurface_frame = 1; bytes xsurface_frame = 1;
} }
......
...@@ -30,37 +30,51 @@ ...@@ -30,37 +30,51 @@
namespace feed { namespace feed {
// Tracks UI changes in |StreamModel| and forwards them to |SurfaceInterface|s. // Tracks UI changes in |StreamModel| and forwards them to |SurfaceInterface|s.
// Has the same lifetime as |StreamModel|. // TODO(harringtond): implement spinner slice.
class FeedStream::ModelMonitor : public StreamModel::Observer { class FeedStream::SurfaceUpdater : public StreamModel::Observer {
public: public:
using ContentRevision = ContentRevision; using ContentRevision = ContentRevision;
ModelMonitor(StreamModel* model, explicit SurfaceUpdater(const base::ObserverList<SurfaceInterface>* surfaces)
const base::ObserverList<SurfaceInterface>* surfaces) : surfaces_(surfaces) {}
: model_(model), surfaces_(surfaces) { ~SurfaceUpdater() override = default;
model_->SetObserver(this); SurfaceUpdater(const SurfaceUpdater&) = delete;
const std::vector<ContentRevision>& content_list = model_->GetContentList(); SurfaceUpdater& operator=(const SurfaceUpdater&) = delete;
current_content_set_.insert(content_list.begin(), content_list.end());
void SetModel(StreamModel* model) {
if (model_ == model)
return;
if (model_)
model_->SetObserver(nullptr);
model_ = model;
if (model_) {
model_->SetObserver(this);
const std::vector<ContentRevision>& content_list =
model_->GetContentList();
current_content_set_.insert(content_list.begin(), content_list.end());
for (SurfaceInterface& surface : *surfaces_) {
surface.StreamUpdate(GetUpdateForNewSurface(*model_));
}
}
} }
~ModelMonitor() override = default;
ModelMonitor(const ModelMonitor&) = delete;
ModelMonitor& operator=(const ModelMonitor&) = delete;
// StreamModel::Observer. // StreamModel::Observer.
void OnUiUpdate(const StreamModel::UiUpdate& update) override { void OnUiUpdate(const StreamModel::UiUpdate& update) override {
// TODO(harringtond): add shared states too. DCHECK(model_); // The update comes from the model.
if (!update.content_list_changed) if (!update.content_list_changed)
return; return;
feedui::StreamUpdate stream_update; feedui::StreamUpdate stream_update;
const std::vector<ContentRevision>& content_list = model_->GetContentList(); const std::vector<ContentRevision>& content_list = model_->GetContentList();
for (ContentRevision content_revision : content_list) { for (ContentRevision content_revision : content_list) {
AddSliceUpdate(content_revision, AddSliceUpdate(*model_, content_revision,
current_content_set_.count(content_revision) == 0, current_content_set_.count(content_revision) == 0,
&stream_update); &stream_update);
} }
for (const StreamModel::UiUpdate::SharedStateInfo& info : for (const StreamModel::UiUpdate::SharedStateInfo& info :
update.shared_states) { update.shared_states) {
if (info.updated) if (info.updated)
AddSharedState(info.shared_state_id, &stream_update); AddSharedState(*model_, info.shared_state_id, &stream_update);
} }
current_content_set_.clear(); current_content_set_.clear();
...@@ -73,7 +87,28 @@ class FeedStream::ModelMonitor : public StreamModel::Observer { ...@@ -73,7 +87,28 @@ class FeedStream::ModelMonitor : public StreamModel::Observer {
// Sends the initial stream state to a newly connected surface. // Sends the initial stream state to a newly connected surface.
void SurfaceAdded(SurfaceInterface* surface) { void SurfaceAdded(SurfaceInterface* surface) {
surface->InitialStreamState(GetUpdateForNewSurface()); if (model_) {
surface->StreamUpdate(GetUpdateForNewSurface(*model_));
}
}
void LoadStreamFailed(LoadStreamStatus load_stream_status) {
auto zero_state_type = feedui::ZeroStateSlice::NO_CARDS_AVAILABLE;
switch (load_stream_status) {
case LoadStreamStatus::kProtoTranslationFailed:
case LoadStreamStatus::kNoResponseBody:
case LoadStreamStatus::kCannotLoadFromNetworkOffline:
case LoadStreamStatus::kCannotLoadFromNetworkThrottled:
zero_state_type = feedui::ZeroStateSlice::CANT_REFRESH;
break;
default:
break;
}
// Note that with multiple surface, it's possible that we send a zero-state
// to a single surface multiple times.
for (SurfaceInterface& surface : *surfaces_) {
SendZeroStateUpdate(zero_state_type, &surface);
}
} }
private: private:
...@@ -83,10 +118,32 @@ class FeedStream::ModelMonitor : public StreamModel::Observer { ...@@ -83,10 +118,32 @@ class FeedStream::ModelMonitor : public StreamModel::Observer {
sizeof(integer_value)); sizeof(integer_value));
} }
void AddSharedState(const std::string& shared_state_id, static feedui::StreamUpdate GetUpdateForNewSurface(const StreamModel& model) {
feedui::StreamUpdate* stream_update) { feedui::StreamUpdate result;
for (ContentRevision content_revision : model.GetContentList()) {
AddSliceUpdate(model, content_revision, /*is_content_new=*/true, &result);
}
for (std::string& id : model.GetSharedStateIds()) {
AddSharedState(model, id, &result);
}
return result;
}
static void SendZeroStateUpdate(feedui::ZeroStateSlice::Type zero_state_type,
SurfaceInterface* surface) {
feedui::StreamUpdate update;
feedui::Slice* slice = update.add_updated_slices()->mutable_slice();
slice->mutable_zero_state_slice()->set_type(zero_state_type);
slice->set_slice_id("zero-state");
surface->StreamUpdate(update);
}
static void AddSharedState(const StreamModel& model,
const std::string& shared_state_id,
feedui::StreamUpdate* stream_update) {
const std::string* shared_state_data = const std::string* shared_state_data =
model_->FindSharedStateData(shared_state_id); model.FindSharedStateData(shared_state_id);
if (!shared_state_data) if (!shared_state_data)
return; return;
feedui::SharedState* added_shared_state = feedui::SharedState* added_shared_state =
...@@ -95,14 +152,15 @@ class FeedStream::ModelMonitor : public StreamModel::Observer { ...@@ -95,14 +152,15 @@ class FeedStream::ModelMonitor : public StreamModel::Observer {
added_shared_state->set_xsurface_shared_state(*shared_state_data); added_shared_state->set_xsurface_shared_state(*shared_state_data);
} }
void AddSliceUpdate(ContentRevision content_revision, static void AddSliceUpdate(const StreamModel& model,
bool is_content_new, ContentRevision content_revision,
feedui::StreamUpdate* stream_update) { bool is_content_new,
feedui::StreamUpdate* stream_update) {
if (is_content_new) { if (is_content_new) {
feedui::Slice* slice = feedui::Slice* slice =
stream_update->add_updated_slices()->mutable_slice(); stream_update->add_updated_slices()->mutable_slice();
slice->set_slice_id(ToSliceId(content_revision)); slice->set_slice_id(ToSliceId(content_revision));
const feedstore::Content* content = model_->FindContent(content_revision); const feedstore::Content* content = model.FindContent(content_revision);
DCHECK(content); DCHECK(content);
slice->mutable_xsurface_slice()->set_xsurface_frame(content->frame()); slice->mutable_xsurface_slice()->set_xsurface_frame(content->frame());
} else { } else {
...@@ -111,20 +169,9 @@ class FeedStream::ModelMonitor : public StreamModel::Observer { ...@@ -111,20 +169,9 @@ class FeedStream::ModelMonitor : public StreamModel::Observer {
} }
} }
feedui::StreamUpdate GetUpdateForNewSurface() {
feedui::StreamUpdate result;
for (ContentRevision content_revision : model_->GetContentList()) {
AddSliceUpdate(content_revision, /*is_content_new=*/true, &result);
}
for (std::string& id : model_->GetSharedStateIds()) {
AddSharedState(id, &result);
}
return result;
}
// Owned by |FeedStream|. // Owned by |FeedStream|.
StreamModel* model_; // Warning!: Null when the model is not yet loaded.
StreamModel* model_ = nullptr;
const base::ObserverList<SurfaceInterface>* surfaces_; const base::ObserverList<SurfaceInterface>* surfaces_;
std::set<ContentRevision> current_content_set_; std::set<ContentRevision> current_content_set_;
...@@ -164,6 +211,8 @@ FeedStream::FeedStream( ...@@ -164,6 +211,8 @@ FeedStream::FeedStream(
static WireResponseTranslator default_translator; static WireResponseTranslator default_translator;
wire_response_translator_ = &default_translator; wire_response_translator_ = &default_translator;
surface_updater_ = std::make_unique<SurfaceUpdater>(&surfaces_);
// Inserting this task first ensures that |store_| is initialized before // Inserting this task first ensures that |store_| is initialized before
// it is used. // it is used.
task_queue_.AddTask(std::make_unique<WaitForStoreInitializeTask>(store_)); task_queue_.AddTask(std::make_unique<WaitForStoreInitializeTask>(store_));
...@@ -182,20 +231,18 @@ void FeedStream::InitializeScheduling() { ...@@ -182,20 +231,18 @@ void FeedStream::InitializeScheduling() {
FeedStream::~FeedStream() = default; FeedStream::~FeedStream() = default;
void FeedStream::TriggerStreamLoad() { void FeedStream::TriggerStreamLoad() {
if (model_monitor_ || model_loading_in_progress_) if (model_ || model_loading_in_progress_)
return; return;
if (!delegate_->IsEulaAccepted()) { // If we should not load the stream, abort and send a zero-state update.
stream_event_observer_->OnLoadStream( if (!IsArticlesListVisible()) {
LoadStreamStatus::kNoStatus, LoadStreamTaskComplete(LoadStreamTask::Result(
LoadStreamStatus::kLoadNotAllowedEulaNotAccepted); LoadStreamStatus::kLoadNotAllowedArticlesListHidden));
return; return;
} }
if (!delegate_->IsEulaAccepted()) {
if (!IsArticlesListVisible()) { LoadStreamTaskComplete(LoadStreamTask::Result(
stream_event_observer_->OnLoadStream( LoadStreamStatus::kLoadNotAllowedEulaNotAccepted));
LoadStreamStatus::kNoStatus,
LoadStreamStatus::kLoadNotAllowedArticlesListHidden);
return; return;
} }
...@@ -212,15 +259,17 @@ void FeedStream::LoadStreamTaskComplete(LoadStreamTask::Result result) { ...@@ -212,15 +259,17 @@ void FeedStream::LoadStreamTaskComplete(LoadStreamTask::Result result) {
<< result.load_from_store_status << result.load_from_store_status
<< " final_status=" << result.final_status; << " final_status=" << result.final_status;
model_loading_in_progress_ = false; model_loading_in_progress_ = false;
// If loading failed, update surfaces with an appropriate zero-state error.
if (!model_) {
surface_updater_->LoadStreamFailed(result.final_status);
}
} }
void FeedStream::AttachSurface(SurfaceInterface* surface) { void FeedStream::AttachSurface(SurfaceInterface* surface) {
surfaces_.AddObserver(surface); surfaces_.AddObserver(surface);
if (model_monitor_) { surface_updater_->SurfaceAdded(surface);
model_monitor_->SurfaceAdded(surface); TriggerStreamLoad();
} else {
TriggerStreamLoad();
}
} }
void FeedStream::DetachSurface(SurfaceInterface* surface) { void FeedStream::DetachSurface(SurfaceInterface* surface) {
...@@ -385,18 +434,14 @@ void FeedStream::MaybeTriggerRefresh(TriggerType trigger, ...@@ -385,18 +434,14 @@ void FeedStream::MaybeTriggerRefresh(TriggerType trigger,
void FeedStream::LoadModel(std::unique_ptr<StreamModel> model) { void FeedStream::LoadModel(std::unique_ptr<StreamModel> model) {
DCHECK(!model_); DCHECK(!model_);
model_ = std::move(model); model_ = std::move(model);
model_monitor_ =
std::make_unique<FeedStream::ModelMonitor>(model_.get(), &surfaces_);
model_->SetStoreObserver(this); model_->SetStoreObserver(this);
for (SurfaceInterface& surface : surfaces_) { surface_updater_->SetModel(model_.get());
model_monitor_->SurfaceAdded(&surface);
}
} }
void FeedStream::UnloadModel() { void FeedStream::UnloadModel() {
if (!model_) if (!model_)
return; return;
model_monitor_.reset(); surface_updater_->SetModel(nullptr);
model_.reset(); model_.reset();
} }
......
...@@ -173,7 +173,7 @@ class FeedStream : public FeedStreamApi, ...@@ -173,7 +173,7 @@ class FeedStream : public FeedStreamApi,
std::unique_ptr<UserClassifier> user_classifier); std::unique_ptr<UserClassifier> user_classifier);
private: private:
class ModelMonitor; class SurfaceUpdater;
class ModelStoreChangeMonitor; class ModelStoreChangeMonitor;
void MaybeTriggerRefresh(TriggerType trigger, void MaybeTriggerRefresh(TriggerType trigger,
bool clear_all_before_refresh = false); bool clear_all_before_refresh = false);
...@@ -202,8 +202,7 @@ class FeedStream : public FeedStreamApi, ...@@ -202,8 +202,7 @@ class FeedStream : public FeedStreamApi,
// Whether the model is being loaded. Used to prevent multiple simultaneous // Whether the model is being loaded. Used to prevent multiple simultaneous
// attempts to load the model. // attempts to load the model.
bool model_loading_in_progress_ = false; bool model_loading_in_progress_ = false;
// Monitors |model_|. Null when |model_| is null. std::unique_ptr<SurfaceUpdater> surface_updater_;
std::unique_ptr<ModelMonitor> model_monitor_;
// The stream model. Null if not yet loaded. // The stream model. Null if not yet loaded.
// Internally, this should only be changed by |LoadModel()| and // Internally, this should only be changed by |LoadModel()| and
// |UnloadModel()|. // |UnloadModel()|.
......
...@@ -95,11 +95,11 @@ std::string ModelStateFor(FeedStore* store) { ...@@ -95,11 +95,11 @@ std::string ModelStateFor(FeedStore* store) {
class TestSurface : public FeedStream::SurfaceInterface { class TestSurface : public FeedStream::SurfaceInterface {
public: public:
// FeedStream::SurfaceInterface. // FeedStream::SurfaceInterface.
void InitialStreamState(const feedui::StreamUpdate& stream_update) override {
initial_state = stream_update;
}
void StreamUpdate(const feedui::StreamUpdate& stream_update) override { void StreamUpdate(const feedui::StreamUpdate& stream_update) override {
if (!initial_state)
initial_state = stream_update;
update = stream_update; update = stream_update;
++update_count_;
} }
// Test functions. // Test functions.
...@@ -107,10 +107,35 @@ class TestSurface : public FeedStream::SurfaceInterface { ...@@ -107,10 +107,35 @@ class TestSurface : public FeedStream::SurfaceInterface {
void Clear() { void Clear() {
initial_state = base::nullopt; initial_state = base::nullopt;
update = base::nullopt; update = base::nullopt;
update_count_ = 0;
}
// Describe what is shown on the surface in a format that can be easily
// asserted against.
std::string Describe() {
if (!initial_state)
return "empty";
if (update->updated_slices().size() == 1 &&
update->updated_slices()[0].has_slice() &&
update->updated_slices()[0].slice().has_zero_state_slice()) {
return "zero-state";
}
std::stringstream ss;
ss << update->updated_slices().size() << " slices";
// If there's more than one update, we want to know that.
if (update_count_ > 1) {
ss << " " << update_count_ << " updates";
}
return ss.str();
} }
base::Optional<feedui::StreamUpdate> initial_state; base::Optional<feedui::StreamUpdate> initial_state;
base::Optional<feedui::StreamUpdate> update; base::Optional<feedui::StreamUpdate> update;
private:
int update_count_ = 0;
}; };
class TestUserClassifier : public UserClassifier { class TestUserClassifier : public UserClassifier {
...@@ -370,9 +395,9 @@ TEST_F(FeedStreamTest, SurfaceReceivesInitialContentLoadedAfterAttach) { ...@@ -370,9 +395,9 @@ TEST_F(FeedStreamTest, SurfaceReceivesInitialContentLoadedAfterAttach) {
stream_->LoadModelForTesting(std::move(model)); stream_->LoadModelForTesting(std::move(model));
} }
ASSERT_TRUE(surface.initial_state); ASSERT_EQ("2 slices", surface.Describe());
const feedui::StreamUpdate& initial_state = surface.initial_state.value(); const feedui::StreamUpdate& initial_state = surface.initial_state.value();
ASSERT_EQ(2, initial_state.updated_slices().size());
EXPECT_NE("", initial_state.updated_slices(0).slice().slice_id()); EXPECT_NE("", initial_state.updated_slices(0).slice().slice_id());
EXPECT_EQ("f:0", initial_state.updated_slices(0) EXPECT_EQ("f:0", initial_state.updated_slices(0)
.slice() .slice()
...@@ -407,7 +432,7 @@ TEST_F(FeedStreamTest, SurfaceReceivesUpdatedContent) { ...@@ -407,7 +432,7 @@ TEST_F(FeedStreamTest, SurfaceReceivesUpdatedContent) {
const feedui::StreamUpdate& initial_state = surface.initial_state.value(); const feedui::StreamUpdate& initial_state = surface.initial_state.value();
const feedui::StreamUpdate& update = surface.update.value(); const feedui::StreamUpdate& update = surface.update.value();
ASSERT_EQ(2, update.updated_slices().size()); ASSERT_EQ("2 slices 2 updates", surface.Describe());
// First slice is just an ID that matches the old 1st slice ID. // First slice is just an ID that matches the old 1st slice ID.
EXPECT_EQ(initial_state.updated_slices(0).slice().slice_id(), EXPECT_EQ(initial_state.updated_slices(0).slice().slice_id(),
update.updated_slices(0).slice_id()); update.updated_slices(0).slice_id());
...@@ -433,7 +458,6 @@ TEST_F(FeedStreamTest, SurfaceReceivesSecondUpdatedContent) { ...@@ -433,7 +458,6 @@ TEST_F(FeedStreamTest, SurfaceReceivesSecondUpdatedContent) {
}); });
// Clear the last update and add #3. // Clear the last update and add #3.
surface.update = {};
stream_->ExecuteOperations({ stream_->ExecuteOperations({
MakeOperation(MakeCluster(3, MakeRootId())), MakeOperation(MakeCluster(3, MakeRootId())),
MakeOperation(MakeContentNode(3, MakeClusterId(3))), MakeOperation(MakeContentNode(3, MakeClusterId(3))),
...@@ -442,7 +466,7 @@ TEST_F(FeedStreamTest, SurfaceReceivesSecondUpdatedContent) { ...@@ -442,7 +466,7 @@ TEST_F(FeedStreamTest, SurfaceReceivesSecondUpdatedContent) {
// The last update should have only one new piece of content. // The last update should have only one new piece of content.
// This verifies the current content set is tracked properly. // This verifies the current content set is tracked properly.
ASSERT_TRUE(surface.update); ASSERT_EQ("4 slices 3 updates", surface.Describe());
ASSERT_EQ(4, surface.update->updated_slices().size()); ASSERT_EQ(4, surface.update->updated_slices().size());
EXPECT_FALSE(surface.update->updated_slices(0).has_slice()); EXPECT_FALSE(surface.update->updated_slices(0).has_slice());
...@@ -464,6 +488,7 @@ TEST_F(FeedStreamTest, DetachSurface) { ...@@ -464,6 +488,7 @@ TEST_F(FeedStreamTest, DetachSurface) {
stream_->AttachSurface(&surface); stream_->AttachSurface(&surface);
EXPECT_TRUE(surface.initial_state); EXPECT_TRUE(surface.initial_state);
stream_->DetachSurface(&surface); stream_->DetachSurface(&surface);
surface.Clear();
// Arbitrary stream change. Surface should not see the update. // Arbitrary stream change. Surface should not see the update.
stream_->ExecuteOperations({ stream_->ExecuteOperations({
...@@ -481,8 +506,7 @@ TEST_F(FeedStreamTest, LoadFromNetwork) { ...@@ -481,8 +506,7 @@ TEST_F(FeedStreamTest, LoadFromNetwork) {
EXPECT_TRUE(network_.query_request_sent); EXPECT_TRUE(network_.query_request_sent);
EXPECT_TRUE(response_translator_.InjectedResponseConsumed()); EXPECT_TRUE(response_translator_.InjectedResponseConsumed());
ASSERT_TRUE(surface.initial_state); EXPECT_EQ("2 slices", surface.Describe());
ASSERT_EQ(2, surface.initial_state->updated_slices().size());
// Verify the model is filled correctly. // Verify the model is filled correctly.
EXPECT_STRINGS_EQUAL(ModelStateFor(MakeTypicalInitialModelState()), EXPECT_STRINGS_EQUAL(ModelStateFor(MakeTypicalInitialModelState()),
stream_->GetModel()->DumpStateForTesting()); stream_->GetModel()->DumpStateForTesting());
...@@ -530,10 +554,10 @@ TEST_F(FeedStreamTest, DoNotLoadFromNetworkWhenOffline) { ...@@ -530,10 +554,10 @@ TEST_F(FeedStreamTest, DoNotLoadFromNetworkWhenOffline) {
TestSurface surface; TestSurface surface;
stream_->AttachSurface(&surface); stream_->AttachSurface(&surface);
WaitForIdleTaskQueue(); WaitForIdleTaskQueue();
// TODO(harringtond): Implement zero-state and check that it is triggered.
EXPECT_EQ(LoadStreamStatus::kCannotLoadFromNetworkOffline, EXPECT_EQ(LoadStreamStatus::kCannotLoadFromNetworkOffline,
event_observer_.load_stream_status); event_observer_.load_stream_status);
EXPECT_FALSE(network_.query_request_sent); EXPECT_EQ("zero-state", surface.Describe());
} }
TEST_F(FeedStreamTest, DoNotLoadStreamWhenArticleListIsHidden) { TEST_F(FeedStreamTest, DoNotLoadStreamWhenArticleListIsHidden) {
...@@ -542,9 +566,10 @@ TEST_F(FeedStreamTest, DoNotLoadStreamWhenArticleListIsHidden) { ...@@ -542,9 +566,10 @@ TEST_F(FeedStreamTest, DoNotLoadStreamWhenArticleListIsHidden) {
TestSurface surface; TestSurface surface;
stream_->AttachSurface(&surface); stream_->AttachSurface(&surface);
WaitForIdleTaskQueue(); WaitForIdleTaskQueue();
// TODO(harringtond): Implement zero-state and check that it is triggered.
EXPECT_EQ(LoadStreamStatus::kLoadNotAllowedArticlesListHidden, EXPECT_EQ(LoadStreamStatus::kLoadNotAllowedArticlesListHidden,
event_observer_.load_stream_status); event_observer_.load_stream_status);
EXPECT_EQ("zero-state", surface.Describe());
} }
TEST_F(FeedStreamTest, DoNotLoadStreamWhenEulaIsNotAccepted) { TEST_F(FeedStreamTest, DoNotLoadStreamWhenEulaIsNotAccepted) {
...@@ -553,10 +578,10 @@ TEST_F(FeedStreamTest, DoNotLoadStreamWhenEulaIsNotAccepted) { ...@@ -553,10 +578,10 @@ TEST_F(FeedStreamTest, DoNotLoadStreamWhenEulaIsNotAccepted) {
TestSurface surface; TestSurface surface;
stream_->AttachSurface(&surface); stream_->AttachSurface(&surface);
WaitForIdleTaskQueue(); WaitForIdleTaskQueue();
// TODO(harringtond): Implement zero-state and check that it is triggered.
EXPECT_FALSE(network_.query_request_sent);
EXPECT_EQ(LoadStreamStatus::kLoadNotAllowedEulaNotAccepted, EXPECT_EQ(LoadStreamStatus::kLoadNotAllowedEulaNotAccepted,
event_observer_.load_stream_status); event_observer_.load_stream_status);
EXPECT_EQ("zero-state", surface.Describe());
} }
TEST_F(FeedStreamTest, DoNotLoadFromNetworkAfterHistoryIsDeleted) { TEST_F(FeedStreamTest, DoNotLoadFromNetworkAfterHistoryIsDeleted) {
...@@ -568,7 +593,8 @@ TEST_F(FeedStreamTest, DoNotLoadFromNetworkAfterHistoryIsDeleted) { ...@@ -568,7 +593,8 @@ TEST_F(FeedStreamTest, DoNotLoadFromNetworkAfterHistoryIsDeleted) {
stream_->AttachSurface(&surface); stream_->AttachSurface(&surface);
WaitForIdleTaskQueue(); WaitForIdleTaskQueue();
ASSERT_FALSE(surface.initial_state); EXPECT_EQ("zero-state", surface.Describe());
EXPECT_EQ(LoadStreamStatus::kCannotLoadFromNetworkSupressedForHistoryDelete, EXPECT_EQ(LoadStreamStatus::kCannotLoadFromNetworkSupressedForHistoryDelete,
event_observer_.load_stream_status); event_observer_.load_stream_status);
...@@ -577,8 +603,7 @@ TEST_F(FeedStreamTest, DoNotLoadFromNetworkAfterHistoryIsDeleted) { ...@@ -577,8 +603,7 @@ TEST_F(FeedStreamTest, DoNotLoadFromNetworkAfterHistoryIsDeleted) {
stream_->AttachSurface(&surface); stream_->AttachSurface(&surface);
WaitForIdleTaskQueue(); WaitForIdleTaskQueue();
ASSERT_TRUE(surface.initial_state); EXPECT_EQ("2 slices 2 updates", surface.Describe());
ASSERT_EQ(2, surface.initial_state->updated_slices().size());
} }
TEST_F(FeedStreamTest, ShouldMakeFeedQueryRequestConsumesQuota) { TEST_F(FeedStreamTest, ShouldMakeFeedQueryRequestConsumesQuota) {
...@@ -602,9 +627,8 @@ TEST_F(FeedStreamTest, LoadStreamFromStore) { ...@@ -602,9 +627,8 @@ TEST_F(FeedStreamTest, LoadStreamFromStore) {
stream_->AttachSurface(&surface); stream_->AttachSurface(&surface);
WaitForIdleTaskQueue(); WaitForIdleTaskQueue();
ASSERT_TRUE(surface.initial_state); ASSERT_EQ("2 slices", surface.Describe());
EXPECT_FALSE(network_.query_request_sent); EXPECT_FALSE(network_.query_request_sent);
EXPECT_EQ(2, surface.initial_state->updated_slices().size());
// Verify the model is filled correctly. // Verify the model is filled correctly.
EXPECT_STRINGS_EQUAL(ModelStateFor(MakeTypicalInitialModelState()), EXPECT_STRINGS_EQUAL(ModelStateFor(MakeTypicalInitialModelState()),
stream_->GetModel()->DumpStateForTesting()); stream_->GetModel()->DumpStateForTesting());
...@@ -617,8 +641,8 @@ TEST_F(FeedStreamTest, DetachSurfaceWhileLoadingModel) { ...@@ -617,8 +641,8 @@ TEST_F(FeedStreamTest, DetachSurfaceWhileLoadingModel) {
stream_->DetachSurface(&surface); stream_->DetachSurface(&surface);
WaitForIdleTaskQueue(); WaitForIdleTaskQueue();
EXPECT_EQ("empty", surface.Describe());
EXPECT_TRUE(network_.query_request_sent); EXPECT_TRUE(network_.query_request_sent);
EXPECT_FALSE(surface.initial_state);
} }
TEST_F(FeedStreamTest, AttachMultipleSurfacesLoadsModelOnce) { TEST_F(FeedStreamTest, AttachMultipleSurfacesLoadsModelOnce) {
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#ifndef COMPONENTS_FEED_CORE_V2_PUBLIC_FEED_STREAM_API_H_ #ifndef COMPONENTS_FEED_CORE_V2_PUBLIC_FEED_STREAM_API_H_
#define COMPONENTS_FEED_CORE_V2_PUBLIC_FEED_STREAM_API_H_ #define COMPONENTS_FEED_CORE_V2_PUBLIC_FEED_STREAM_API_H_
#include <vector>
#include "base/observer_list_types.h" #include "base/observer_list_types.h"
#include "base/util/type_safety/id_type.h" #include "base/util/type_safety/id_type.h"
#include "components/feed/core/proto/v2/wire/content_id.pb.h" #include "components/feed/core/proto/v2/wire/content_id.pb.h"
...@@ -30,10 +32,8 @@ class FeedStreamApi { ...@@ -30,10 +32,8 @@ class FeedStreamApi {
public: public:
class SurfaceInterface : public base::CheckedObserver { class SurfaceInterface : public base::CheckedObserver {
public: public:
// Called once after registering the observer. Provides complete // Called after registering the observer to provide the full stream state.
// initial state. // Also called whenever the stream changes.
virtual void InitialStreamState(const feedui::StreamUpdate&) = 0;
// Called only after InitialStreamState, for each subsequent update.
virtual void StreamUpdate(const feedui::StreamUpdate&) = 0; virtual void StreamUpdate(const feedui::StreamUpdate&) = 0;
}; };
......
...@@ -57,7 +57,8 @@ const feedstore::Content* StreamModel::FindContent( ...@@ -57,7 +57,8 @@ const feedstore::Content* StreamModel::FindContent(
ContentRevision revision) const { ContentRevision revision) const {
return GetFinalFeatureTree()->FindContent(revision); return GetFinalFeatureTree()->FindContent(revision);
} }
const std::string* StreamModel::FindSharedStateData(const std::string& id) { const std::string* StreamModel::FindSharedStateData(
const std::string& id) const {
auto iter = shared_states_.find(id); auto iter = shared_states_.find(id);
if (iter != shared_states_.end()) { if (iter != shared_states_.end()) {
return &iter->second.data; return &iter->second.data;
......
...@@ -96,7 +96,7 @@ class StreamModel { ...@@ -96,7 +96,7 @@ class StreamModel {
const feedstore::Content* FindContent(ContentRevision revision) const; const feedstore::Content* FindContent(ContentRevision revision) const;
// Returns the shared state data identified by |id|. // Returns the shared state data identified by |id|.
const std::string* FindSharedStateData(const std::string& id); const std::string* FindSharedStateData(const std::string& id) const;
// Apply |operations| to the model. // Apply |operations| to the model.
void ExecuteOperations(std::vector<feedstore::DataOperation> operations); void ExecuteOperations(std::vector<feedstore::DataOperation> operations);
......
...@@ -25,6 +25,9 @@ class FeedStream; ...@@ -25,6 +25,9 @@ class FeedStream;
class LoadStreamTask : public offline_pages::Task { class LoadStreamTask : public offline_pages::Task {
public: public:
struct Result { struct Result {
Result() = default;
explicit Result(LoadStreamStatus a_final_status)
: final_status(a_final_status) {}
// Final status of loading the stream. // Final status of loading the stream.
LoadStreamStatus final_status = LoadStreamStatus::kNoStatus; LoadStreamStatus final_status = LoadStreamStatus::kNoStatus;
// Status of just loading the stream from the persistent store, if that // Status of just loading the stream from the persistent store, if that
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment