blob: 6c26e10ab9904854dc545daacd8eec1cb3dcdbaf [file] [log] [blame]
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/user_notes/browser/user_note_service.h"
#include <memory>
#include <vector>
#include "base/unguessable_token.h"
#include "components/user_notes/browser/frame_user_note_changes.h"
#include "components/user_notes/browser/user_note_base_test.h"
#include "components/user_notes/browser/user_note_manager.h"
#include "components/user_notes/browser/user_note_service.h"
#include "components/user_notes/interfaces/user_note_service_delegate.h"
#include "components/user_notes/interfaces/user_notes_ui.h"
#include "components/user_notes/model/user_note_metadata.h"
#include "components/user_notes/model/user_note_model_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using testing::_;
using testing::Invoke;
using testing::Mock;
namespace user_notes {
using IdList = std::vector<base::UnguessableToken>;
class MockUserNoteServiceDelegate : public UserNoteServiceDelegate {
public:
MOCK_METHOD(std::vector<content::RenderFrameHost*>,
GetAllFramesForUserNotes,
(),
(override));
MOCK_METHOD(UserNotesUI*,
GetUICoordinatorForFrame,
(const content::RenderFrameHost* rfh),
(override));
MOCK_METHOD(bool,
IsFrameInActiveTab,
(const content::RenderFrameHost* rfh),
(override));
void SetFramesForUserNotes(
const std::vector<content::RenderFrameHost*>& frames) {
frames_ = frames;
}
std::vector<content::RenderFrameHost*> MockGetAllFramesForUserNotes() {
return frames_;
}
private:
std::vector<content::RenderFrameHost*> frames_;
};
class MockUserNoteStorage : public UserNoteStorage {
public:
MOCK_METHOD(void,
GetNoteMetadataForUrls,
(const std::vector<GURL>& urls,
base::OnceCallback<void(UserNoteMetadataSnapshot)> callback),
(override));
MOCK_METHOD(void,
GetNotesById,
(const IdList& ids,
base::OnceCallback<void(std::vector<std::unique_ptr<UserNote>>)>
callback),
(override));
// The following must be mocked even though they're not used in the tests,
// because they're abstract.
MOCK_METHOD(void,
UpdateNote,
(const UserNote* model,
std::string note_body_text,
bool is_creation),
(override));
MOCK_METHOD(void,
DeleteNote,
(const base::UnguessableToken& guid),
(override));
MOCK_METHOD(void, DeleteAllForUrl, (const GURL& url), (override));
MOCK_METHOD(void,
DeleteAllForOrigin,
(const url::Origin& origin),
(override));
MOCK_METHOD(void, DeleteAllNotes, (), (override));
const std::vector<GURL>& requested_metadata_urls() {
return requested_metadata_urls_;
}
const IdList& requested_model_ids() { return requested_model_ids_; }
void MockGetNoteMetadataForUrls(
const std::vector<GURL>& urls,
base::OnceCallback<void(UserNoteMetadataSnapshot)> callback) {
requested_metadata_urls_ = urls;
std::move(callback).Run(UserNoteMetadataSnapshot());
}
void MockGetNotesById(
const IdList& ids,
base::OnceCallback<void(std::vector<std::unique_ptr<UserNote>>)>
callback) {
requested_model_ids_ = ids;
std::move(callback).Run({});
}
private:
std::vector<GURL> requested_metadata_urls_;
IdList requested_model_ids_;
};
// Partially mock the object under test so tests can control side effects.
class MockUserNoteService : public UserNoteService {
public:
MockUserNoteService(std::unique_ptr<UserNoteServiceDelegate> delegate,
std::unique_ptr<UserNoteStorage> storage)
: UserNoteService(std::move(delegate), std::move(storage)) {}
const UserNoteService::IdSet& computed_new_notes() {
return computed_new_notes_;
}
const IdList& changes_applied() { return changes_applied_; }
MOCK_METHOD(void,
OnNoteMetadataFetched,
(const std::vector<content::RenderFrameHost*>& all_frames,
UserNoteMetadataSnapshot metadata_snapshot),
(override));
MOCK_METHOD(void,
OnNoteModelsFetched,
(const UserNoteService::IdSet& new_notes,
std::vector<std::unique_ptr<FrameUserNoteChanges>> note_changes,
std::vector<std::unique_ptr<UserNote>> notes),
(override));
MOCK_METHOD(void,
OnFrameChangesApplied,
(base::UnguessableToken change_id),
(override));
void MockOnNoteModelsFetched(
const UserNoteService::IdSet& new_notes,
std::vector<std::unique_ptr<FrameUserNoteChanges>> note_changes,
std::vector<std::unique_ptr<UserNote>> notes) {
computed_new_notes_ = new_notes;
}
void MockOnFrameChangesApplied(base::UnguessableToken change_id) {
changes_applied_.emplace_back(change_id);
}
void CallBaseClassOnNoteMetadataFetched(
const std::vector<content::RenderFrameHost*>& all_frames,
UserNoteMetadataSnapshot metadata_snapshot) {
UserNoteService::OnNoteMetadataFetched(all_frames,
std::move(metadata_snapshot));
}
void CallBaseClassOnNoteModelsFetched(
const UserNoteService::IdSet& new_notes,
std::vector<std::unique_ptr<FrameUserNoteChanges>> note_changes,
std::vector<std::unique_ptr<UserNote>> notes) {
UserNoteService::OnNoteModelsFetched(new_notes, std::move(note_changes),
std::move(notes));
}
void CallBaseClassOnFrameChangesApplied(base::UnguessableToken change_id) {
UserNoteService::OnFrameChangesApplied(change_id);
}
private:
UserNoteService::IdSet computed_new_notes_;
IdList changes_applied_;
};
class MockUserNoteInstance : public UserNoteInstance {
public:
explicit MockUserNoteInstance(base::SafeRef<UserNote> model_ref,
UserNoteManager* manager)
: UserNoteInstance(model_ref, manager) {}
void InitializeHighlightInternal() override {
DidFinishAttachment(gfx::Rect());
}
};
class MockFrameUserNoteChanges : public FrameUserNoteChanges {
public:
MockFrameUserNoteChanges(base::SafeRef<UserNoteService> service,
content::RenderFrameHost* rfh,
const ChangeList& notes_added,
const ChangeList& notes_modified,
const ChangeList& notes_removed)
: FrameUserNoteChanges(service,
rfh,
notes_added,
notes_modified,
notes_removed) {}
private:
std::unique_ptr<UserNoteInstance> MakeNoteInstance(
const UserNote* note_model,
UserNoteManager* manager) const override {
return std::make_unique<MockUserNoteInstance>(note_model->GetSafeRef(),
manager);
}
};
class MockUserNotesUI : public UserNotesUI {
public:
MOCK_METHOD(void, Invalidate, (), (override));
// The following methods are not used for these tests but they still need to
// be mocked because they are sbstract.
MOCK_METHOD(void,
FocusNote,
(const base::UnguessableToken& guid),
(override));
MOCK_METHOD(void,
StartNoteCreation,
(UserNoteInstance * instance),
(override));
MOCK_METHOD(void, Show, (), (override));
};
class UserNoteServiceTest : public UserNoteBaseTest {
protected:
void SetUp() override {
UserNoteBaseTest::SetUp();
AddNewNotesToService(2);
}
void CreateService() override {
auto service_delegate = std::make_unique<MockUserNoteServiceDelegate>();
service_delegate_ = service_delegate.get();
auto storage = std::make_unique<MockUserNoteStorage>();
EXPECT_CALL(*storage, UpdateNote).Times(0);
EXPECT_CALL(*storage, DeleteNote).Times(0);
EXPECT_CALL(*storage, DeleteAllForUrl).Times(0);
EXPECT_CALL(*storage, DeleteAllForOrigin).Times(0);
EXPECT_CALL(*storage, DeleteAllNotes).Times(0);
storage_ = storage.get();
note_service_ = std::make_unique<MockUserNoteService>(
std::move(service_delegate), std::move(storage));
mock_service_ = (MockUserNoteService*)note_service_.get();
}
std::vector<content::RenderFrameHost*> GetAllFramesInUse() {
std::vector<content::RenderFrameHost*> frames;
for (const std::unique_ptr<content::WebContents>& wc : web_contents_list_) {
frames.emplace_back(wc->GetPrimaryMainFrame());
}
return frames;
}
raw_ptr<MockUserNoteService> mock_service_;
raw_ptr<MockUserNoteServiceDelegate> service_delegate_;
raw_ptr<MockUserNoteStorage> storage_;
};
// Tests that note models are returned correctly by the service.
TEST_F(UserNoteServiceTest, GetNoteModel) {
// Verify initial state.
EXPECT_EQ(ModelMapSize(), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 0u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 0u);
// Getting existing note models should return the expected model.
const UserNote* model1 = note_service_->GetNoteModel(note_ids_[0]);
const UserNote* model2 = note_service_->GetNoteModel(note_ids_[1]);
ASSERT_TRUE(model1);
ASSERT_TRUE(model2);
EXPECT_EQ(model1->id(), note_ids_[0]);
EXPECT_EQ(model2->id(), note_ids_[1]);
// Getting a note model that doesn't exist should return `nullptr` and not
// crash.
EXPECT_EQ(note_service_->GetNoteModel(base::UnguessableToken::Create()),
nullptr);
}
// Tests that references to note managers are correctly added to the model map.
TEST_F(UserNoteServiceTest, OnNoteInstanceAddedToPage) {
// Verify initial state.
EXPECT_EQ(ModelMapSize(), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 0u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 0u);
// Simulate note instances being created in managers.
UserNoteManager* m1 = ConfigureNewManager();
UserNoteManager* m2 = ConfigureNewManager();
note_service_->OnNoteInstanceAddedToPage(note_ids_[0], m1);
note_service_->OnNoteInstanceAddedToPage(note_ids_[0], m2);
note_service_->OnNoteInstanceAddedToPage(note_ids_[1], m1);
EXPECT_EQ(ModelMapSize(), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
}
// Tests that references to note managers are correctly removed from the model
// map.
TEST_F(UserNoteServiceTest, OnNoteInstanceRemovedFromPage) {
// Initial setup.
UserNoteManager* m1 = ConfigureNewManager();
UserNoteManager* m2 = ConfigureNewManager();
note_service_->OnNoteInstanceAddedToPage(note_ids_[0], m1);
note_service_->OnNoteInstanceAddedToPage(note_ids_[0], m2);
note_service_->OnNoteInstanceAddedToPage(note_ids_[1], m1);
// Verify initial state.
EXPECT_EQ(ModelMapSize(), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
// Simulate a note instance being removed from a page. Its ref should be
// removed from the model map, but only for the removed note.
note_service_->OnNoteInstanceRemovedFromPage(note_ids_[0], m1);
EXPECT_EQ(ModelMapSize(), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
// Simulate the last instance of a note being removed from its page. Its model
// should be cleaned up from the model map.
note_service_->OnNoteInstanceRemovedFromPage(note_ids_[0], m2);
EXPECT_EQ(ModelMapSize(), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
EXPECT_FALSE(DoesModelExist(note_ids_[0]));
// Repeat with the other note instance.
note_service_->OnNoteInstanceRemovedFromPage(note_ids_[1], m1);
EXPECT_EQ(ModelMapSize(), 0u);
EXPECT_FALSE(DoesModelExist(note_ids_[0]));
EXPECT_FALSE(DoesModelExist(note_ids_[1]));
}
// Tests that partial notes are correctly identified as such.
TEST_F(UserNoteServiceTest, IsNoteInProgress) {
EXPECT_EQ(CreationMapSize(), 0u);
AddPartialNotesToService(2);
EXPECT_EQ(CreationMapSize(), 2u);
EXPECT_FALSE(note_service_->IsNoteInProgress(note_ids_[0]));
EXPECT_FALSE(note_service_->IsNoteInProgress(note_ids_[1]));
EXPECT_TRUE(note_service_->IsNoteInProgress(note_ids_[2]));
EXPECT_TRUE(note_service_->IsNoteInProgress(note_ids_[3]));
// The method should also return false for notes that don't exist.
EXPECT_FALSE(
note_service_->IsNoteInProgress(base::UnguessableToken::Create()));
}
// Tests that adding an instance of a partial note to a page does not impact
// the model map and the note manager references.
TEST_F(UserNoteServiceTest, AddPartialNoteInstance) {
// Initial setup.
UserNoteManager* manager = ConfigureNewManager();
note_service_->OnNoteInstanceAddedToPage(note_ids_[0], manager);
note_service_->OnNoteInstanceAddedToPage(note_ids_[1], manager);
// Verify initial setup.
EXPECT_EQ(ModelMapSize(), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
// Create an in-progress note.
EXPECT_EQ(CreationMapSize(), 0u);
AddPartialNotesToService(1);
EXPECT_EQ(CreationMapSize(), 1u);
// Simulate the instance of the in-progress note being added to the note
// manager.
note_service_->OnNoteInstanceAddedToPage(note_ids_[2], manager);
// Verify the model map hasn't been impacted and that the creation map is
// still as expected.
EXPECT_EQ(ModelMapSize(), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
EXPECT_EQ(CreationMapSize(), 1u);
EXPECT_TRUE(note_service_->IsNoteInProgress(note_ids_[2]));
}
// Tests that removing an instance of a partial note from a page does not impact
// the model map and the note manager references, and correctly clears the
// partial note from the creation map.
TEST_F(UserNoteServiceTest, RemovePartialNoteInstance) {
// Initial setup.
UserNoteManager* manager = ConfigureNewManager();
note_service_->OnNoteInstanceAddedToPage(note_ids_[0], manager);
note_service_->OnNoteInstanceAddedToPage(note_ids_[1], manager);
// Verify initial setup.
EXPECT_EQ(ModelMapSize(), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
// Create an in-progress note.
EXPECT_EQ(CreationMapSize(), 0u);
AddPartialNotesToService(1);
EXPECT_EQ(CreationMapSize(), 1u);
// Simulate the instance of the in-progress note being removed from the note
// manager.
note_service_->OnNoteInstanceRemovedFromPage(note_ids_[2], manager);
// Verify the model map hasn't been impacted and the partial note has been
// removed from the creation map.
EXPECT_EQ(ModelMapSize(), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
EXPECT_EQ(CreationMapSize(), 0u);
EXPECT_FALSE(note_service_->IsNoteInProgress(note_ids_[2]));
}
// Tests that the service requests the metadata snapshot for the right URLs when
// it gets notified that notes have changed on disk.
TEST_F(UserNoteServiceTest, OnNotesChanged) {
// Initial setup.
AddNewNotesToService(2);
UserNoteManager* manager1 = ConfigureNewManager();
UserNoteManager* manager2 = ConfigureNewManager();
AddNewInstanceToManager(manager1, note_ids_[0]);
AddNewInstanceToManager(manager1, note_ids_[1]);
AddNewInstanceToManager(manager2, note_ids_[2]);
AddNewInstanceToManager(manager2, note_ids_[3]);
service_delegate_->SetFramesForUserNotes(GetAllFramesInUse());
// Verify initial setup.
EXPECT_EQ(ModelMapSize(), 4u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[2]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[3]), 1u);
EXPECT_TRUE(DoesManagerExistForId(note_ids_[0], manager1));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[1], manager1));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[2], manager2));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[3], manager2));
// Configure service delegate mock.
EXPECT_CALL(*service_delegate_, GetAllFramesForUserNotes)
.Times(1)
.WillOnce(
Invoke(service_delegate_.get(),
&MockUserNoteServiceDelegate::MockGetAllFramesForUserNotes));
EXPECT_CALL(*service_delegate_, GetUICoordinatorForFrame).Times(0);
EXPECT_CALL(*service_delegate_, IsFrameInActiveTab).Times(0);
// Configure storage mock.
EXPECT_CALL(*storage_, GetNoteMetadataForUrls)
.Times(1)
.WillOnce(Invoke(storage_.get(),
&MockUserNoteStorage::MockGetNoteMetadataForUrls));
EXPECT_CALL(*storage_, GetNotesById).Times(0);
// Configure service mock.
EXPECT_CALL(*mock_service_, OnNoteMetadataFetched).Times(1);
EXPECT_CALL(*mock_service_, OnNoteModelsFetched).Times(0);
EXPECT_CALL(*mock_service_, OnFrameChangesApplied).Times(0);
// Simulate notes changing on disk.
note_service_->OnNotesChanged();
// Mocks ensure callbacks are invoked synchronously, so expectations can be
// immediately verified.
const std::vector<GURL>& fetched_urls = storage_->requested_metadata_urls();
for (size_t i = 0; i < fetched_urls.size(); ++i) {
EXPECT_EQ(
fetched_urls[i],
web_contents_list_[i]->GetPrimaryMainFrame()->GetLastCommittedURL());
}
}
// Tests that the service requests the right models from the storage after
// receiving the metadata snapshot.
TEST_F(UserNoteServiceTest, OnNoteMetadataFetched) {
// Initial setup.
AddNewNotesToService(2);
AddPartialNotesToService(1);
UserNoteManager* manager1 = ConfigureNewManager();
UserNoteManager* manager2 = ConfigureNewManager();
AddNewInstanceToManager(manager1, note_ids_[0]);
AddNewInstanceToManager(manager1, note_ids_[1]);
AddNewInstanceToManager(manager2, note_ids_[2]);
AddNewInstanceToManager(manager2, note_ids_[3]);
// Verify initial setup.
EXPECT_EQ(ModelMapSize(), 4u);
EXPECT_EQ(CreationMapSize(), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[2]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[3]), 1u);
EXPECT_TRUE(DoesManagerExistForId(note_ids_[0], manager1));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[1], manager1));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[2], manager2));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[3], manager2));
// Configure service delegate mock.
EXPECT_CALL(*service_delegate_, GetAllFramesForUserNotes).Times(0);
EXPECT_CALL(*service_delegate_, GetUICoordinatorForFrame).Times(0);
EXPECT_CALL(*service_delegate_, IsFrameInActiveTab).Times(0);
// Configure storage mock.
EXPECT_CALL(*storage_, GetNoteMetadataForUrls).Times(0);
EXPECT_CALL(*storage_, GetNotesById)
.Times(1)
.WillOnce(Invoke(storage_.get(), &MockUserNoteStorage::MockGetNotesById));
// Configure service mock.
EXPECT_CALL(*mock_service_, OnNoteMetadataFetched)
.Times(1)
.WillOnce(
Invoke(mock_service_.get(),
&MockUserNoteService::CallBaseClassOnNoteMetadataFetched));
EXPECT_CALL(*mock_service_, OnNoteModelsFetched)
.Times(1)
.WillOnce(Invoke(mock_service_.get(),
&MockUserNoteService::MockOnNoteModelsFetched));
EXPECT_CALL(*mock_service_, OnFrameChangesApplied).Times(0);
// Create a fake metadata snapshot with some updated and new notes, including
// one new note that wasn't in the creation map so simulate receiving it from
// Sync.
note_ids_.emplace_back(base::UnguessableToken::Create());
UserNoteMetadataSnapshot snapshot;
GURL url1 =
web_contents_list_[0]->GetPrimaryMainFrame()->GetLastCommittedURL();
GURL url2 =
web_contents_list_[1]->GetPrimaryMainFrame()->GetLastCommittedURL();
snapshot.AddEntry(
url1, note_ids_[0],
std::make_unique<UserNoteMetadata>(base::Time::Now(), base::Time::Now(),
/*min_note_version=*/1));
snapshot.AddEntry(
url1, note_ids_[4],
std::make_unique<UserNoteMetadata>(base::Time::Now(), base::Time::Now(),
/*min_note_version=*/1));
snapshot.AddEntry(
url2, note_ids_[2],
std::make_unique<UserNoteMetadata>(base::Time::Now(), base::Time::Now(),
/*min_note_version=*/1));
snapshot.AddEntry(
url2, note_ids_[5],
std::make_unique<UserNoteMetadata>(base::Time::Now(), base::Time::Now(),
/*min_note_version=*/1));
// Simulate the storage returning the metadata snapshot to the service
// callback.
note_service_->OnNoteMetadataFetched(GetAllFramesInUse(),
std::move(snapshot));
// Mocks ensure callbacks are invoked synchronously, so expectations can be
// immediately verified.
const IdList& fetched_ids = storage_->requested_model_ids();
EXPECT_EQ(fetched_ids.size(), 4u);
EXPECT_NE(std::find(fetched_ids.begin(), fetched_ids.end(), note_ids_[0]),
fetched_ids.end());
EXPECT_NE(std::find(fetched_ids.begin(), fetched_ids.end(), note_ids_[2]),
fetched_ids.end());
EXPECT_NE(std::find(fetched_ids.begin(), fetched_ids.end(), note_ids_[4]),
fetched_ids.end());
EXPECT_NE(std::find(fetched_ids.begin(), fetched_ids.end(), note_ids_[5]),
fetched_ids.end());
const UserNoteService::IdSet& computed_new_notes =
mock_service_->computed_new_notes();
EXPECT_EQ(computed_new_notes.size(), 2u);
EXPECT_NE(computed_new_notes.find(note_ids_[4]), computed_new_notes.end());
EXPECT_NE(computed_new_notes.find(note_ids_[5]), computed_new_notes.end());
}
// Tests that the service correctly updates the models in the model map and
// applies the necessary note changes.
TEST_F(UserNoteServiceTest, OnNoteModelsFetched) {
// Initial setup.
AddNewNotesToService(2);
AddPartialNotesToService(1);
UserNoteManager* manager1 = ConfigureNewManager();
UserNoteManager* manager2 = ConfigureNewManager();
AddNewInstanceToManager(manager1, note_ids_[0]);
AddNewInstanceToManager(manager1, note_ids_[1]);
AddNewInstanceToManager(manager2, note_ids_[2]);
AddNewInstanceToManager(manager2, note_ids_[3]);
// For note 1 to be correctly deleted later in the test, its target URL must
// be changed to match the parent frame.
content::RenderFrameHost* frame1 =
web_contents_list_[0]->GetPrimaryMainFrame();
const auto& note_1_entry_it = note_service_->model_map_.find(note_ids_[1]);
note_1_entry_it->second.model->Update(std::make_unique<UserNote>(
note_ids_[1], GetTestUserNoteMetadata(), GetTestUserNoteBody(),
GetTestUserNotePageTarget(frame1->GetLastCommittedURL().spec())));
// Verify initial setup.
EXPECT_EQ(ModelMapSize(), 4u);
EXPECT_EQ(CreationMapSize(), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[2]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[3]), 1u);
EXPECT_TRUE(DoesManagerExistForId(note_ids_[0], manager1));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[1], manager1));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[2], manager2));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[3], manager2));
// Configure service delegate mock.
EXPECT_CALL(*service_delegate_, GetAllFramesForUserNotes).Times(0);
EXPECT_CALL(*service_delegate_, GetUICoordinatorForFrame).Times(0);
EXPECT_CALL(*service_delegate_, IsFrameInActiveTab).Times(0);
// Configure storage mock.
EXPECT_CALL(*storage_, GetNoteMetadataForUrls).Times(0);
EXPECT_CALL(*storage_, GetNotesById).Times(0);
// Configure service mock.
EXPECT_CALL(*mock_service_, OnNoteMetadataFetched).Times(0);
EXPECT_CALL(*mock_service_, OnNoteModelsFetched)
.Times(1)
.WillOnce(Invoke(mock_service_.get(),
&MockUserNoteService::CallBaseClassOnNoteModelsFetched));
EXPECT_CALL(*mock_service_, OnFrameChangesApplied)
.Times(2)
.WillRepeatedly(Invoke(mock_service_.get(),
&MockUserNoteService::MockOnFrameChangesApplied));
// Prepare the fake input for passing to the method under test. 2 notes are
// simulated as having been updated, 2 notes as created, and one as having
// been removed. One of the created notes is not present in the creation map
// to simulate receiving it from Sync.
note_ids_.emplace_back(base::UnguessableToken::Create());
UserNoteService::IdSet new_notes;
new_notes.emplace(note_ids_[4]);
new_notes.emplace(note_ids_[5]);
content::RenderFrameHost* frame2 =
web_contents_list_[1]->GetPrimaryMainFrame();
auto change1 = std::make_unique<MockFrameUserNoteChanges>(
note_service_->GetSafeRef(), frame1, /*added=*/IdList{note_ids_[4]},
/*modified=*/IdList{note_ids_[0]}, /*removed=*/IdList{note_ids_[1]});
auto change2 = std::make_unique<MockFrameUserNoteChanges>(
note_service_->GetSafeRef(), frame2, /*added=*/IdList{note_ids_[5]},
/*modified=*/IdList{note_ids_[2]}, /*removed=*/IdList{});
base::UnguessableToken change1_id = change1->id();
base::UnguessableToken change2_id = change2->id();
std::vector<std::unique_ptr<FrameUserNoteChanges>> note_changes;
note_changes.emplace_back(std::move(change1));
note_changes.emplace_back(std::move(change2));
const std::string kText0 = "updated note 0";
const std::string kText2 = "updated note 2";
const std::string kText4 = "new note 4";
const std::string kText5 = "new note 5";
const std::string url1 = frame1->GetLastCommittedURL().spec();
const std::string url2 = frame2->GetLastCommittedURL().spec();
auto note0 = std::make_unique<UserNote>(
note_ids_[0], GetTestUserNoteMetadata(),
std::make_unique<UserNoteBody>(kText0), GetTestUserNotePageTarget(url1));
auto note2 = std::make_unique<UserNote>(
note_ids_[2], GetTestUserNoteMetadata(),
std::make_unique<UserNoteBody>(kText2), GetTestUserNotePageTarget(url2));
auto note4 = std::make_unique<UserNote>(
note_ids_[4], GetTestUserNoteMetadata(),
std::make_unique<UserNoteBody>(kText4), GetTestUserNotePageTarget(url1));
auto note5 = std::make_unique<UserNote>(
note_ids_[5], GetTestUserNoteMetadata(),
std::make_unique<UserNoteBody>(kText5), GetTestUserNotePageTarget(url2));
std::vector<std::unique_ptr<UserNote>> note_models;
note_models.emplace_back(std::move(note0));
note_models.emplace_back(std::move(note4));
note_models.emplace_back(std::move(note2));
note_models.emplace_back(std::move(note5));
// Simulate the storage returning the updated note models to the service
// callback.
note_service_->OnNoteModelsFetched(new_notes, std::move(note_changes),
std::move(note_models));
// Mocks ensure callbacks are invoked synchronously, so expectations can be
// immediately verified.
const IdList& changes_applied = mock_service_->changes_applied();
EXPECT_EQ(changes_applied.size(), 2u);
EXPECT_NE(
std::find(changes_applied.begin(), changes_applied.end(), change1_id),
changes_applied.end());
EXPECT_NE(
std::find(changes_applied.begin(), changes_applied.end(), change2_id),
changes_applied.end());
EXPECT_EQ(ModelMapSize(), 5u);
EXPECT_EQ(CreationMapSize(), 0u);
EXPECT_FALSE(DoesModelExist(note_ids_[1]));
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[2]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[3]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[4]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[5]), 1u);
EXPECT_TRUE(DoesManagerExistForId(note_ids_[0], manager1));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[2], manager2));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[3], manager2));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[4], manager1));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[5], manager2));
std::string actual_text_0 =
note_service_->GetNoteModel(note_ids_[0])->body().plain_text_value();
std::string actual_text_2 =
note_service_->GetNoteModel(note_ids_[2])->body().plain_text_value();
std::string actual_text_4 =
note_service_->GetNoteModel(note_ids_[4])->body().plain_text_value();
std::string actual_text_5 =
note_service_->GetNoteModel(note_ids_[5])->body().plain_text_value();
EXPECT_EQ(actual_text_0, kText0);
EXPECT_EQ(actual_text_2, kText2);
EXPECT_EQ(actual_text_4, kText4);
EXPECT_EQ(actual_text_5, kText5);
EXPECT_EQ(note_service_->note_changes_in_progress_.size(), 2u);
EXPECT_NE(note_service_->note_changes_in_progress_.find(change1_id),
note_service_->note_changes_in_progress_.end());
EXPECT_NE(note_service_->note_changes_in_progress_.find(change2_id),
note_service_->note_changes_in_progress_.end());
}
// Tests that the service correctly finalizes frame changes that have been
// applied and notifies the UI to update itself when needed.
TEST_F(UserNoteServiceTest, OnFrameChangesApplied) {
// Initial setup.
AddNewNotesToService(2);
AddPartialNotesToService(1);
UserNoteManager* manager1 = ConfigureNewManager();
UserNoteManager* manager2 = ConfigureNewManager();
AddNewInstanceToManager(manager1, note_ids_[0]);
AddNewInstanceToManager(manager1, note_ids_[1]);
AddNewInstanceToManager(manager2, note_ids_[2]);
AddNewInstanceToManager(manager2, note_ids_[3]);
content::RenderFrameHost* frame1 =
web_contents_list_[0]->GetPrimaryMainFrame();
content::RenderFrameHost* frame2 =
web_contents_list_[1]->GetPrimaryMainFrame();
auto change1 = std::make_unique<FrameUserNoteChanges>(
note_service_->GetSafeRef(), frame1, /*added=*/IdList{},
/*modified=*/IdList{note_ids_[0]}, /*removed=*/IdList{});
auto change2 = std::make_unique<FrameUserNoteChanges>(
note_service_->GetSafeRef(), frame2, /*added=*/IdList{},
/*modified=*/IdList{note_ids_[2]}, /*removed=*/IdList{});
base::UnguessableToken change1_id = change1->id();
base::UnguessableToken change2_id = change2->id();
note_service_->note_changes_in_progress_.emplace(change1_id,
std::move(change1));
note_service_->note_changes_in_progress_.emplace(change2_id,
std::move(change2));
// Verify initial setup.
EXPECT_EQ(ModelMapSize(), 4u);
EXPECT_EQ(CreationMapSize(), 1u);
EXPECT_EQ(note_service_->note_changes_in_progress_.size(), 2u);
EXPECT_EQ(ManagerCountForId(note_ids_[0]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[1]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[2]), 1u);
EXPECT_EQ(ManagerCountForId(note_ids_[3]), 1u);
EXPECT_TRUE(DoesManagerExistForId(note_ids_[0], manager1));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[1], manager1));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[2], manager2));
EXPECT_TRUE(DoesManagerExistForId(note_ids_[3], manager2));
// Configure UI mock.
auto mock_ui = std::make_unique<MockUserNotesUI>();
EXPECT_CALL(*mock_ui, Invalidate).Times(1);
EXPECT_CALL(*mock_ui, FocusNote).Times(0);
EXPECT_CALL(*mock_ui, StartNoteCreation).Times(0);
EXPECT_CALL(*mock_ui, Show).Times(0);
// Configure service delegate mock.
EXPECT_CALL(*service_delegate_, GetAllFramesForUserNotes).Times(0);
EXPECT_CALL(*service_delegate_, GetUICoordinatorForFrame(_))
.Times(1)
.WillOnce(Invoke([&mock_ui](const content::RenderFrameHost* frame) {
return mock_ui.get();
}));
EXPECT_CALL(*service_delegate_, IsFrameInActiveTab(_))
.Times(2)
.WillOnce(
Invoke([](const content::RenderFrameHost* frame) { return true; }))
.WillOnce(
Invoke([](const content::RenderFrameHost* frame) { return false; }));
// Configure storage mock.
EXPECT_CALL(*storage_, GetNoteMetadataForUrls).Times(0);
EXPECT_CALL(*storage_, GetNotesById).Times(0);
// Configure service mock.
EXPECT_CALL(*mock_service_, OnNoteMetadataFetched).Times(0);
EXPECT_CALL(*mock_service_, OnNoteModelsFetched).Times(0);
EXPECT_CALL(*mock_service_, OnFrameChangesApplied)
.Times(2)
.WillRepeatedly(
Invoke(mock_service_.get(),
&MockUserNoteService::CallBaseClassOnFrameChangesApplied));
// Simulate the first change being applied. It should invalidate the UI since
// it is simulated as being in an active tab.
note_service_->OnFrameChangesApplied(change1_id);
EXPECT_EQ(ModelMapSize(), 4u);
EXPECT_EQ(CreationMapSize(), 1u);
EXPECT_EQ(note_service_->note_changes_in_progress_.size(), 1u);
EXPECT_EQ(note_service_->note_changes_in_progress_.find(change1_id),
note_service_->note_changes_in_progress_.end());
EXPECT_NE(note_service_->note_changes_in_progress_.find(change2_id),
note_service_->note_changes_in_progress_.end());
Mock::VerifyAndClearExpectations(mock_ui.get());
// Simulate the second change being applied.
EXPECT_CALL(*mock_ui, Invalidate).Times(0);
EXPECT_CALL(*mock_ui, FocusNote).Times(0);
EXPECT_CALL(*mock_ui, StartNoteCreation).Times(0);
EXPECT_CALL(*mock_ui, Show).Times(0);
note_service_->OnFrameChangesApplied(change2_id);
EXPECT_EQ(ModelMapSize(), 4u);
EXPECT_EQ(CreationMapSize(), 1u);
EXPECT_EQ(note_service_->note_changes_in_progress_.size(), 0u);
}
} // namespace user_notes