Commit 182d3add authored by Clemens Arbesser's avatar Clemens Arbesser Committed by Commit Bot

Reland "[Autofill Assistant] Added ForEach interaction."

This is a reland of ae61a59a

I reproduced the build failure and added the missing include.

Original change's description:
> [Autofill Assistant] Added ForEach interaction.
>
> Note: this is an alternative solution for http://crrev/c/2235698
>
> This interaction executes a number of callbacks for the input loop value. This is intended to be used to inflate UI elements for client-only values, i.e., for values that the backend can't specify.
>
> Internally, ForEach loops are implemented by introducing the concept of callback contexts, which will change value and view lookup accordingly.
>
> In particular, callback contexts are used to automatically replace placeholders of the form ${i} in value and view identifiers (where 'i' is the loop identifier). This allows creating and referencing values and views with templated names, such as "created_view_${i}" and "value[${i}]".
>
> Bug: b/145043394
> Change-Id: I53089252fe1cc14b2b1fb74cfc56d7314bc4b37c
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2241975
> Commit-Queue: Clemens Arbesser <arbesser@google.com>
> Reviewed-by: Sandro Maggi <sandromaggi@google.com>
> Reviewed-by: Marian Fechete <marianfe@google.com>
> Cr-Commit-Position: refs/heads/master@{#780785}

Bug: b/145043394
Change-Id: I36b64f8d5a64f66f9e9081137a61a20c31ade8cd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2259847Reviewed-by: default avatarMathias Carlen <mcarlen@chromium.org>
Commit-Queue: Clemens Arbesser <arbesser@google.com>
Cr-Commit-Position: refs/heads/master@{#781253}
parent 7298ff01
......@@ -489,16 +489,17 @@ GenericUiControllerAndroid::~GenericUiControllerAndroid() {
std::unique_ptr<GenericUiControllerAndroid>
GenericUiControllerAndroid::CreateFromProto(
const GenericUserInterfaceProto& proto,
const std::map<std::string, std::string> context,
base::android::ScopedJavaGlobalRef<jobject> jcontext,
base::android::ScopedJavaGlobalRef<jobject> jdelegate,
EventHandler* event_handler,
UserModel* user_model,
BasicInteractions* basic_interactions) {
// Create view layout.
auto view_handler = std::make_unique<ViewHandlerAndroid>();
auto view_handler = std::make_unique<ViewHandlerAndroid>(context);
auto interaction_handler = std::make_unique<InteractionHandlerAndroid>(
event_handler, user_model, basic_interactions, view_handler.get(),
jcontext, jdelegate);
context, event_handler, user_model, basic_interactions,
view_handler.get(), jcontext, jdelegate);
JNIEnv* env = base::android::AttachCurrentThread();
auto jroot_view =
proto.has_root_view()
......
......@@ -28,6 +28,7 @@ class GenericUiControllerAndroid {
// Ownership of the arguments is not changed.
static std::unique_ptr<GenericUiControllerAndroid> CreateFromProto(
const GenericUserInterfaceProto& proto,
const std::map<std::string, std::string> context,
base::android::ScopedJavaGlobalRef<jobject> jcontext,
base::android::ScopedJavaGlobalRef<jobject> jdelegate,
EventHandler* event_handler,
......
......@@ -14,6 +14,7 @@
#include "chrome/browser/android/autofill_assistant/generic_ui_interactions_android.h"
#include "chrome/browser/android/autofill_assistant/view_handler_android.h"
#include "components/autofill_assistant/browser/basic_interactions.h"
#include "components/autofill_assistant/browser/field_formatter.h"
#include "components/autofill_assistant/browser/generic_ui.pb.h"
#include "components/autofill_assistant/browser/ui_delegate.h"
#include "components/autofill_assistant/browser/user_model.h"
......@@ -21,14 +22,113 @@
namespace autofill_assistant {
namespace {
// Helper RAII class that sets the execution context for callbacks and unsets
// the context upon deletion. Simply unsetting the context after running the
// callbacks is unsafe, as a callback may have ended the action, thus deleting
// the context and leading to a crash.
class SetExecutionContext {
public:
SetExecutionContext(base::WeakPtr<UserModel> user_model,
base::WeakPtr<ViewHandlerAndroid> view_handler,
const std::map<std::string, std::string>& context)
: user_model_(user_model),
view_handler_(view_handler),
context_(context) {
if (user_model_ != nullptr) {
user_model_->AddIdentifierPlaceholders(context_);
}
if (view_handler_ != nullptr) {
view_handler_->AddIdentifierPlaceholders(context_);
}
}
~SetExecutionContext() {
if (user_model_ != nullptr) {
user_model_->RemoveIdentifierPlaceholders(context_);
}
if (view_handler_ != nullptr) {
view_handler_->RemoveIdentifierPlaceholders(context_);
}
}
private:
base::WeakPtr<UserModel> user_model_;
base::WeakPtr<ViewHandlerAndroid> view_handler_;
std::map<std::string, std::string> context_;
};
// Runs |callbacks| using the context provided by |interaction_handler| and
// |additional_context|.
// Note: parameters are passed by value, as their owner may go out of scope
// before all callbacks have been processed.
void RunWithContext(
std::vector<InteractionHandlerAndroid::InteractionCallback> callbacks,
std::map<std::string, std::string> additional_context,
base::WeakPtr<InteractionHandlerAndroid> interaction_handler,
base::WeakPtr<UserModel> user_model,
base::WeakPtr<ViewHandlerAndroid> view_handler) {
if (!interaction_handler || !user_model || !view_handler) {
return;
}
// Context is set via RAII to ensure that it is properly unset when done.
interaction_handler->AddContext(additional_context);
SetExecutionContext set_context(user_model, view_handler,
interaction_handler->GetContext());
for (const auto& callback : callbacks) {
callback.Run();
// A callback may have caused |interaction_handler| to go out of scope.
if (!interaction_handler) {
return;
}
}
if (interaction_handler != nullptr) {
interaction_handler->RemoveContext(additional_context);
}
}
void RunForEachLoop(
const ForEachProto& proto,
const std::vector<InteractionHandlerAndroid::InteractionCallback>&
callbacks,
base::WeakPtr<InteractionHandlerAndroid> interaction_handler,
base::WeakPtr<UserModel> user_model,
base::WeakPtr<ViewHandlerAndroid> view_handler) {
if (!interaction_handler || !user_model || !view_handler) {
return;
}
auto loop_value = user_model->GetValue(proto.loop_value_model_identifier());
if (!loop_value.has_value()) {
VLOG(2) << "Error running ForEach loop: "
<< proto.loop_value_model_identifier() << " not found in model";
return;
}
for (int i = 0; i < GetValueSize(*loop_value); ++i) {
// Temporarily add "<loop_counter> -> i" to execution context.
// Note: interactions may create nested UI instances. Those instances
// will inherit their parents' current context, which includes the
// placeholder for the loop variable currently being iterated.
RunWithContext(callbacks, /* additional_context = */
{{proto.loop_counter(), base::NumberToString(i)}},
interaction_handler, user_model, view_handler);
}
}
} // namespace
InteractionHandlerAndroid::InteractionHandlerAndroid(
const std::map<std::string, std::string>& context,
EventHandler* event_handler,
UserModel* user_model,
BasicInteractions* basic_interactions,
ViewHandlerAndroid* view_handler,
base::android::ScopedJavaGlobalRef<jobject> jcontext,
base::android::ScopedJavaGlobalRef<jobject> jdelegate)
: event_handler_(event_handler),
: context_(context),
event_handler_(event_handler),
user_model_(user_model),
basic_interactions_(basic_interactions),
view_handler_(view_handler),
......@@ -54,6 +154,20 @@ void InteractionHandlerAndroid::StopListening() {
is_listening_ = false;
}
void InteractionHandlerAndroid::AddContext(
const std::map<std::string, std::string>& context) {
for (const auto& value : context) {
context_[value.first] = value.second;
}
}
void InteractionHandlerAndroid::RemoveContext(
const std::map<std::string, std::string>& context) {
for (const auto& value : context) {
context_.erase(value.first);
}
}
UserModel* InteractionHandlerAndroid::GetUserModel() const {
return user_model_;
}
......@@ -101,9 +215,11 @@ void InteractionHandlerAndroid::AddInteraction(
void InteractionHandlerAndroid::OnEvent(const EventHandler::EventKey& key) {
auto it = interactions_.find(key);
if (it != interactions_.end()) {
for (auto& callback : it->second) {
callback.Run();
}
RunWithContext(it->second, /* additional_context = */ {},
this->GetWeakPtr(), user_model_->GetWeakPtr(),
view_handler_->GetWeakPtr());
// Note: it is unsafe to call any code after running callbacks, because
// a callback may effectively delete *this.
}
}
......@@ -277,13 +393,45 @@ InteractionHandlerAndroid::CreateInteractionCallbackFromProto(
base::BindRepeating(&android_interactions::ClearViewContainer,
proto.clear_view_container().view_identifier(),
view_handler_, jdelegate_));
case CallbackProto::kForEach: {
if (proto.for_each().loop_counter().empty()) {
VLOG(1) << "Error creating ForEach interaction: "
"loop_counter not set";
return base::nullopt;
}
if (proto.for_each().loop_value_model_identifier().empty()) {
VLOG(1) << "Error creating ForEach interaction: "
"loop_value_model_identifier not set";
return base::nullopt;
}
std::vector<InteractionHandlerAndroid::InteractionCallback> callbacks;
for (const auto& callback_proto : proto.for_each().callbacks()) {
auto callback = CreateInteractionCallbackFromProto(callback_proto);
if (!callback.has_value()) {
VLOG(1) << "Error creating ForEach interaction: failed to create "
"callback";
return base::nullopt;
}
callbacks.emplace_back(*callback);
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&RunForEachLoop, proto.for_each(), callbacks, GetWeakPtr(),
user_model_->GetWeakPtr(), view_handler_->GetWeakPtr()));
}
case CallbackProto::KIND_NOT_SET:
VLOG(1) << "Error creating interaction: kind not set";
return base::nullopt;
}
}
void InteractionHandlerAndroid::DeleteNestedUi(const std::string& identifier) {
void InteractionHandlerAndroid::DeleteNestedUi(const std::string& input) {
// Replace all placeholders in the input.
auto formatted_identifier = field_formatter::FormatString(input, context_);
if (!formatted_identifier.has_value()) {
VLOG(2) << "Error deleting nested UI: placeholder not found for " << input;
return;
}
std::string identifier = *formatted_identifier;
auto it = nested_ui_controllers_.find(identifier);
if (it != nested_ui_controllers_.end()) {
nested_ui_controllers_.erase(it);
......@@ -292,7 +440,14 @@ void InteractionHandlerAndroid::DeleteNestedUi(const std::string& identifier) {
const GenericUiControllerAndroid* InteractionHandlerAndroid::CreateNestedUi(
const GenericUserInterfaceProto& proto,
const std::string& identifier) {
const std::string& input) {
// Replace all placeholders in the input.
auto formatted_identifier = field_formatter::FormatString(input, context_);
if (!formatted_identifier.has_value()) {
VLOG(2) << "Error creating nested UI: placeholder not found for " << input;
return nullptr;
}
std::string identifier = *formatted_identifier;
if (nested_ui_controllers_.find(identifier) != nested_ui_controllers_.end()) {
VLOG(2) << "Error creating nested UI: " << identifier
<< " already exixsts (did you forget to clear the previous "
......@@ -300,7 +455,7 @@ const GenericUiControllerAndroid* InteractionHandlerAndroid::CreateNestedUi(
return nullptr;
}
auto nested_ui = GenericUiControllerAndroid::CreateFromProto(
proto, jcontext_, jdelegate_, event_handler_, user_model_,
proto, context_, jcontext_, jdelegate_, event_handler_, user_model_,
basic_interactions_);
const auto* nested_ui_ptr = nested_ui.get();
if (nested_ui) {
......
......@@ -36,6 +36,7 @@ class InteractionHandlerAndroid : public EventHandler::Observer {
// Constructor. All dependencies must outlive this instance.
InteractionHandlerAndroid(
const std::map<std::string, std::string>& context,
EventHandler* event_handler,
UserModel* user_model,
BasicInteractions* basic_interactions,
......@@ -49,6 +50,15 @@ class InteractionHandlerAndroid : public EventHandler::Observer {
void StartListening();
void StopListening();
// Adds |context| to the current context of this interaction handler.
void AddContext(const std::map<std::string, std::string>& context);
// Removes the keys in |context| from this handler's context.
void RemoveContext(const std::map<std::string, std::string>& context);
// Returns a copy of the current context.
std::map<std::string, std::string> GetContext() const { return context_; }
// Access to the user model that this interaction handler is bound to.
UserModel* GetUserModel() const;
......@@ -103,6 +113,11 @@ class InteractionHandlerAndroid : public EventHandler::Observer {
std::map<EventHandler::EventKey, std::vector<InteractionCallback>>
interactions_;
// These key-value pairs specify context variables that the handler will use
// to resolve views and values. Nested instances will inherit their parents'
// context variables. Special interactions, such as ForEach, may modify the
// context while they are being executed.
std::map<std::string, std::string> context_;
EventHandler* event_handler_ = nullptr;
UserModel* user_model_ = nullptr;
BasicInteractions* basic_interactions_ = nullptr;
......
......@@ -1691,7 +1691,8 @@ UiControllerAndroid::CreateGenericUiControllerForProto(
auto jcontext =
Java_AutofillAssistantUiController_getContext(env, java_object_);
return GenericUiControllerAndroid::CreateFromProto(
proto, base::android::ScopedJavaGlobalRef<jobject>(jcontext),
proto, /* context = */ {},
base::android::ScopedJavaGlobalRef<jobject>(jcontext),
generic_ui_delegate_.GetJavaObject(), ui_delegate_->GetEventHandler(),
ui_delegate_->GetUserModel(), ui_delegate_->GetBasicInteractions());
}
......
......@@ -3,14 +3,28 @@
// found in the LICENSE file.
#include "chrome/browser/android/autofill_assistant/view_handler_android.h"
#include "components/autofill_assistant/browser/field_formatter.h"
namespace autofill_assistant {
ViewHandlerAndroid::ViewHandlerAndroid() = default;
ViewHandlerAndroid::ViewHandlerAndroid(
const std::map<std::string, std::string>& identifier_placeholders)
: identifier_placeholders_(identifier_placeholders) {}
ViewHandlerAndroid::~ViewHandlerAndroid() = default;
base::WeakPtr<ViewHandlerAndroid> ViewHandlerAndroid::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
base::Optional<base::android::ScopedJavaGlobalRef<jobject>>
ViewHandlerAndroid::GetView(const std::string& view_identifier) const {
ViewHandlerAndroid::GetView(const std::string& input) const {
// Replace all placeholders in the input.
auto formatted_identifier =
field_formatter::FormatString(input, identifier_placeholders_);
if (!formatted_identifier.has_value()) {
return base::nullopt;
}
std::string view_identifier = *formatted_identifier;
auto it = views_.find(view_identifier);
if (it == views_.end()) {
return base::nullopt;
......@@ -20,10 +34,31 @@ ViewHandlerAndroid::GetView(const std::string& view_identifier) const {
// Adds a view to the set of managed views.
void ViewHandlerAndroid::AddView(
const std::string& view_identifier,
const std::string& input,
base::android::ScopedJavaGlobalRef<jobject> jview) {
// Replace all placeholders in the input.
auto formatted_identifier =
field_formatter::FormatString(input, identifier_placeholders_);
if (!formatted_identifier.has_value()) {
return;
}
std::string view_identifier = *formatted_identifier;
DCHECK(views_.find(view_identifier) == views_.end());
views_.emplace(view_identifier, jview);
}
void ViewHandlerAndroid::AddIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders) {
for (const auto& placeholder : placeholders) {
identifier_placeholders_[placeholder.first] = placeholder.second;
}
}
void ViewHandlerAndroid::RemoveIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders) {
for (const auto& placeholder : placeholders) {
identifier_placeholders_.erase(placeholder.first);
}
}
} // namespace autofill_assistant
......@@ -10,6 +10,7 @@
#include <string>
#include "base/android/jni_android.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
namespace autofill_assistant {
......@@ -17,13 +18,18 @@ namespace autofill_assistant {
// Manages a map of view-identifier -> android view instances.
class ViewHandlerAndroid {
public:
ViewHandlerAndroid();
explicit ViewHandlerAndroid(
const std::map<std::string, std::string>& identifier_placeholders);
~ViewHandlerAndroid();
ViewHandlerAndroid(const ViewHandlerAndroid&) = delete;
ViewHandlerAndroid& operator=(const ViewHandlerAndroid&) = delete;
base::WeakPtr<ViewHandlerAndroid> GetWeakPtr();
// Returns the view associated with |view_identifier| or base::nullopt if
// there is no such view.
// -Placeholders in |view_identifier| of the form ${key} are automatically
// replaced (see |AddIdentifierPlaceholders|).
base::Optional<base::android::ScopedJavaGlobalRef<jobject>> GetView(
const std::string& view_identifier) const;
......@@ -31,8 +37,21 @@ class ViewHandlerAndroid {
void AddView(const std::string& view_identifier,
base::android::ScopedJavaGlobalRef<jobject> jview);
// Adds a set of placeholders (overwrite if necessary). When looking up views
// by identifier, all occurrences of ${key} are automatically replaced by
// their value. Example: the current set of placeholders contains "i" -> "1".
// Looking up the view "view_${i}" will now actually lookup "view_1".
void AddIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders);
// Removes a set of placeholders.
void RemoveIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders);
private:
std::map<std::string, base::android::ScopedJavaGlobalRef<jobject>> views_;
std::map<std::string, std::string> identifier_placeholders_;
base::WeakPtrFactory<ViewHandlerAndroid> weak_ptr_factory_{this};
};
} // namespace autofill_assistant
......
......@@ -51,6 +51,7 @@ message CallbackProto {
ShowGenericUiPopupProto show_generic_popup = 13;
CreateNestedGenericUiProto create_nested_ui = 14;
ClearViewContainerProto clear_view_container = 15;
ForEachProto for_each = 16;
}
// Optional model identifier pointing to a single boolean. If set, the
// callback will only be invoked if the condition is true.
......@@ -392,3 +393,18 @@ message ClearViewContainerProto {
// The view container to clear.
optional string view_identifier = 1;
}
// Invokes |callbacks| for each item in the loop value. Automatically replaces
// instances of "${i}" in model and view identifiers with the loop counter.
message ForEachProto {
// The loop counter, usually "i", "j", etc. Callbacks may use this counter
// in view and model identifiers by using "${i}"" placeholders, e.g.,
// "profiles[${i}]" or "my_view_${i}".
optional string loop_counter = 1;
// The value list to loop over.
optional string loop_value_model_identifier = 2;
// The callbacks to invoke for every iteration of the loop.
repeated CallbackProto callbacks = 3;
}
......@@ -3,7 +3,9 @@
// found in the LICENSE file.
#include "components/autofill_assistant/browser/user_model.h"
#include "components/autofill_assistant/browser/field_formatter.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "third_party/re2/src/re2/re2.h"
......@@ -55,9 +57,17 @@ base::WeakPtr<UserModel> UserModel::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void UserModel::SetValue(const std::string& identifier,
void UserModel::SetValue(const std::string& input,
const ValueProto& value,
bool force_notification) {
// Replace all placeholders in the input.
auto formatted_identifier =
field_formatter::FormatString(input, identifier_placeholders_);
if (!formatted_identifier.has_value()) {
VLOG(2) << "Error setting value: placeholder not found for " << input;
return;
}
std::string identifier = *formatted_identifier;
auto result = values_.emplace(identifier, value);
if (!force_notification && !result.second && result.first->second == value &&
value.is_client_side_only() ==
......@@ -72,8 +82,14 @@ void UserModel::SetValue(const std::string& identifier,
}
}
base::Optional<ValueProto> UserModel::GetValue(
const std::string& identifier) const {
base::Optional<ValueProto> UserModel::GetValue(const std::string& input) const {
// Replace all placeholders in the input.
auto formatted_identifier =
field_formatter::FormatString(input, identifier_placeholders_);
if (!formatted_identifier.has_value()) {
return base::nullopt;
}
std::string identifier = *formatted_identifier;
auto it = values_.find(identifier);
if (it != values_.end()) {
return it->second;
......@@ -145,6 +161,20 @@ void UserModel::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void UserModel::AddIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders) {
for (const auto& placeholder : placeholders) {
identifier_placeholders_[placeholder.first] = placeholder.second;
}
}
void UserModel::RemoveIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders) {
for (const auto& placeholder : placeholders) {
identifier_placeholders_.erase(placeholder.first);
}
}
void UserModel::SetAutofillCreditCards(
std::unique_ptr<std::vector<std::unique_ptr<autofill::CreditCard>>>
credit_cards) {
......
......@@ -48,8 +48,10 @@ class UserModel {
bool force_notification = false);
// Returns the value for |identifier| or nullopt if there is no such value.
// Also supports the array operator to retrieve a specific element of a list,
// e.g., "identifier[0]" to get the first item.
// - Placeholders in |identifier| of the form ${key} are automatically
// replaced (see |AddIdentifierPlaceholders|).
// - Also supports the array operator to retrieve
// a specific element of a list, e.g., "identifier[0]" to get the first item.
base::Optional<ValueProto> GetValue(const std::string& identifier) const;
// Returns the value for |reference| or nullopt if there is no such value.
......@@ -72,6 +74,17 @@ class UserModel {
return values;
}
// Adds a set of placeholders (overwrite if necessary). When looking up values
// by identifier, all occurrences of ${key} are automatically replaced by
// their value. Example: the current set of placeholders contains "i" -> "1".
// Looking up the value "value[${i}]" will now actually lookup "value[1]".
void AddIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders);
// Removes a set of placeholders.
void RemoveIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders);
// Replaces the set of available autofill credit cards.
void SetAutofillCreditCards(
std::unique_ptr<std::vector<std::unique_ptr<autofill::CreditCard>>>
......@@ -105,6 +118,7 @@ class UserModel {
friend class UserModelTest;
std::map<std::string, ValueProto> values_;
std::map<std::string, std::string> identifier_placeholders_;
std::map<std::string, std::unique_ptr<autofill::CreditCard>> credit_cards_;
std::map<std::string, std::unique_ptr<autofill::AutofillProfile>> profiles_;
base::ObserverList<Observer> observers_;
......
......@@ -366,4 +366,75 @@ TEST_F(UserModelTest, ClientSideOnlyNotifications) {
EXPECT_TRUE(GetValues().at("identifier").is_client_side_only());
}
TEST_F(UserModelTest, GetValueWithPlaceholders) {
ValueProto value;
value.mutable_strings()->add_values("a");
value.mutable_strings()->add_values("b");
value.mutable_strings()->add_values("c");
model_.SetValue("multi_value", value);
model_.SetValue("single_value_0", SimpleValue(std::string("d")));
model_.SetValue("single_value_1", SimpleValue(std::string("e")));
model_.SetValue("single_value_2", SimpleValue(std::string("f")));
EXPECT_EQ(model_.GetValue("multi_value[${i}]"), base::nullopt);
EXPECT_EQ(model_.GetValue("single_value_i"), base::nullopt);
model_.AddIdentifierPlaceholders({{"i", "0"}});
EXPECT_EQ(model_.GetValue("multi_value[${i}]"),
SimpleValue(std::string("a")));
EXPECT_EQ(model_.GetValue("single_value_${i}"),
SimpleValue(std::string("d")));
// Add placeholder.
model_.AddIdentifierPlaceholders({{"j", "1"}});
EXPECT_EQ(model_.GetValue("multi_value[${j}]"),
SimpleValue(std::string("b")));
EXPECT_EQ(model_.GetValue("single_value_${j}"),
SimpleValue(std::string("e")));
EXPECT_EQ(model_.GetValue("single_value_${j}[${i}]"),
SimpleValue(std::string("e")));
// Overwrite placeholder.
model_.AddIdentifierPlaceholders({{"i", "2"}});
EXPECT_EQ(model_.GetValue("multi_value[${i}]"),
SimpleValue(std::string("c")));
EXPECT_EQ(model_.GetValue("single_value_${i}"),
SimpleValue(std::string("f")));
EXPECT_EQ(model_.GetValue("single_value_${j}[${i}]"), base::nullopt);
// Remove placeholder (the value does not matter, it's just about the key).
model_.RemoveIdentifierPlaceholders({{"i", "123"}});
EXPECT_EQ(model_.GetValue("multi_value[${i}]"), base::nullopt);
EXPECT_EQ(model_.GetValue("single_value_${i}"), base::nullopt);
EXPECT_EQ(model_.GetValue("single_value_${j}"),
SimpleValue(std::string("e")));
}
TEST_F(UserModelTest, SetValueWithPlaceholders) {
ValueProto value;
value.mutable_strings()->add_values("a");
value.mutable_strings()->add_values("b");
value.mutable_strings()->add_values("c");
model_.SetValue("value_${i}", value);
EXPECT_EQ(model_.GetValue("value_${i}"), base::nullopt);
model_.AddIdentifierPlaceholders({{"i", "0"}});
model_.SetValue("value_${i}", value);
EXPECT_EQ(model_.GetValue("value_0"), value);
EXPECT_EQ(model_.GetValue("value_${i}"), value);
model_.RemoveIdentifierPlaceholders({{"i", "0"}});
EXPECT_EQ(model_.GetValue("value_0"), value);
EXPECT_EQ(model_.GetValue("value_${i}"), base::nullopt);
model_.AddIdentifierPlaceholders({{"i", "0"}});
model_.AddIdentifierPlaceholders({{"j", "1"}});
model_.SetValue("value_${i}_${j}", value);
EXPECT_EQ(model_.GetValue("value_0_1"), value);
EXPECT_EQ(model_.GetValue("value_${i}_${j}"), value);
model_.RemoveIdentifierPlaceholders({{"j", "1"}});
EXPECT_EQ(model_.GetValue("value_${i}_${j}"), base::nullopt);
model_.SetValue("value_${i}", value);
EXPECT_EQ(model_.GetValue("value_${i}"), value);
}
} // namespace autofill_assistant
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