blob: 441ea54e8aa374ca085939d8375e0af942f6f3aa [file] [log] [blame]
// Copyright 2018 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/autofill_assistant/browser/element_area.h"
#include <algorithm>
#include <map>
#include <ostream>
#include "base/bind.h"
#include "base/strings/stringprintf.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "components/autofill_assistant/browser/fake_script_executor_delegate.h"
#include "components/autofill_assistant/browser/mock_run_once_callback.h"
#include "components/autofill_assistant/browser/mock_web_controller.h"
#include "components/autofill_assistant/browser/script_executor_delegate.h"
#include "testing/gmock/include/gmock/gmock.h"
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
namespace autofill_assistant {
// User-friendly RectF string representation for matchers.
//
// operator<< must not be in an anonymous namespace to be usable in all
// matchers.
std::string ToString(const RectF& rect) {
return base::StringPrintf("RectF(%2.2f, %2.2f, %2.2f, %2.2f)", rect.left,
rect.top, rect.right, rect.bottom);
}
std::ostream& operator<<(std::ostream& out, const RectF& rectf) {
return out << ToString(rectf);
}
namespace {
MATCHER_P4(MatchingRectF,
left,
top,
right,
bottom,
ToString(RectF{left, top, right, bottom})) {
if (abs(left - arg.left) < 0.01 && abs(top - arg.top) < 0.01 &&
abs(right - arg.right) < 0.01 && abs(bottom - arg.bottom) < 0.01) {
return true;
}
*result_listener << arg;
return false;
}
MATCHER(EmptyRectF, "EmptyRectF") {
if (arg.empty())
return true;
*result_listener << arg;
return false;
}
ACTION(DoNothing) {}
class ElementAreaTest : public testing::Test {
protected:
ElementAreaTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME),
element_area_(&delegate_) {
delegate_.SetWebController(&mock_web_controller_);
ON_CALL(mock_web_controller_, OnGetElementPosition(_, _))
.WillByDefault(RunOnceCallback<1>(false, RectF()));
element_area_.SetOnUpdate(base::BindRepeating(&ElementAreaTest::OnUpdate,
base::Unretained(this)));
}
void SetElement(const std::string& selector) {
ElementAreaProto area;
area.add_rectangles()->add_elements()->add_selectors(selector);
element_area_.SetFromProto(area);
}
void OnUpdate(const std::vector<RectF>& area) { reported_area_ = area; }
// scoped_task_environment_ must be first to guarantee other field
// creation run in that environment.
base::test::ScopedTaskEnvironment scoped_task_environment_;
MockWebController mock_web_controller_;
FakeScriptExecutorDelegate delegate_;
ElementArea element_area_;
std::vector<RectF> reported_area_;
};
TEST_F(ElementAreaTest, Empty) {
EXPECT_THAT(reported_area_, IsEmpty());
std::vector<RectF> rectangles;
element_area_.GetRectangles(&rectangles);
EXPECT_THAT(rectangles, IsEmpty());
}
TEST_F(ElementAreaTest, ElementNotFound) {
SetElement("#not_found");
EXPECT_THAT(reported_area_, ElementsAre(EmptyRectF()));
std::vector<RectF> rectangles;
element_area_.GetRectangles(&rectangles);
EXPECT_THAT(rectangles, ElementsAre(EmptyRectF()));
}
TEST_F(ElementAreaTest, OneRectangle) {
EXPECT_CALL(mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#found"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.25f, 0.25f, 0.75f, 0.75f)));
SetElement("#found");
std::vector<RectF> rectangles;
element_area_.GetRectangles(&rectangles);
EXPECT_THAT(rectangles,
ElementsAre(MatchingRectF(0.25f, 0.25f, 0.75f, 0.75f)));
}
TEST_F(ElementAreaTest, CallOnUpdate) {
EXPECT_CALL(mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#found"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.25f, 0.25f, 0.75f, 0.75f)));
SetElement("#found");
EXPECT_THAT(reported_area_,
ElementsAre(MatchingRectF(0.25f, 0.25f, 0.75f, 0.75f)));
}
TEST_F(ElementAreaTest, TwoRectangles) {
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#top_left"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.0f, 0.25f, 0.25f)));
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#bottom_right"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.25f, 0.25f, 1.0f, 1.0f)));
ElementAreaProto area_proto;
area_proto.add_rectangles()->add_elements()->add_selectors("#top_left");
area_proto.add_rectangles()->add_elements()->add_selectors("#bottom_right");
element_area_.SetFromProto(area_proto);
std::vector<RectF> rectangles;
element_area_.GetRectangles(&rectangles);
EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.0f, 0.0f, 0.25f, 0.25f),
MatchingRectF(0.25f, 0.25f, 1.0f, 1.0f)));
}
TEST_F(ElementAreaTest, OneRectangleTwoElements) {
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element1"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.1f, 0.3f, 0.2f, 0.4f)));
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element2"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.5f, 0.2f, 0.6f, 0.5f)));
ElementAreaProto area_proto;
auto* rectangle_proto = area_proto.add_rectangles();
rectangle_proto->add_elements()->add_selectors("#element1");
rectangle_proto->add_elements()->add_selectors("#element2");
element_area_.SetFromProto(area_proto);
std::vector<RectF> rectangles;
element_area_.GetRectangles(&rectangles);
EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.1f, 0.2f, 0.6f, 0.5f)));
}
TEST_F(ElementAreaTest, DoNotReportIncompleteRectangles) {
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element1"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.1f, 0.3f, 0.2f, 0.4f)));
// Getting the position of #element2 neither succeeds nor fails, simulating an
// intermediate state which shouldn't be reported to the callback.
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element2"}).MustBeVisible()), _))
.WillOnce(DoNothing()); // overrides default action
ElementAreaProto area_proto;
auto* rectangle_proto = area_proto.add_rectangles();
rectangle_proto->add_elements()->add_selectors("#element1");
rectangle_proto->add_elements()->add_selectors("#element2");
element_area_.SetFromProto(area_proto);
EXPECT_THAT(reported_area_, ElementsAre(EmptyRectF()));
std::vector<RectF> rectangles;
element_area_.GetRectangles(&rectangles);
EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.1f, 0.3f, 0.2f, 0.4f)));
}
TEST_F(ElementAreaTest, OneRectangleFourElements) {
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element1"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.0f, 0.1f, 0.1f)));
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element2"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.9f, 0.9f, 1.0f, 1.0f)));
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element3"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.9f, 0.1f, 1.0f)));
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element4"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.9f, 0.0f, 1.0f, 0.1f)));
ElementAreaProto area_proto;
auto* rectangle_proto = area_proto.add_rectangles();
rectangle_proto->add_elements()->add_selectors("#element1");
rectangle_proto->add_elements()->add_selectors("#element2");
rectangle_proto->add_elements()->add_selectors("#element3");
rectangle_proto->add_elements()->add_selectors("#element4");
element_area_.SetFromProto(area_proto);
std::vector<RectF> rectangles;
element_area_.GetRectangles(&rectangles);
EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.0f, 0.0f, 1.0f, 1.0f)));
}
TEST_F(ElementAreaTest, OneRectangleMissingElementsReported) {
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element1"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.1f, 0.1f, 0.2f, 0.2f)));
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element2"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(false, RectF()));
ElementAreaProto area_proto;
auto* rectangle_proto = area_proto.add_rectangles();
rectangle_proto->add_elements()->add_selectors("#element1");
rectangle_proto->add_elements()->add_selectors("#element2");
element_area_.SetFromProto(area_proto);
std::vector<RectF> rectangles;
element_area_.GetRectangles(&rectangles);
EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.1f, 0.1f, 0.2f, 0.2f)));
EXPECT_THAT(reported_area_,
ElementsAre(MatchingRectF(0.1f, 0.1f, 0.2f, 0.2f)));
}
TEST_F(ElementAreaTest, FullWidthRectangle) {
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element1"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.1f, 0.3f, 0.2f, 0.4f)));
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element2"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.5f, 0.7f, 0.6f, 0.8f)));
ElementAreaProto area_proto;
auto* rectangle_proto = area_proto.add_rectangles();
rectangle_proto->add_elements()->add_selectors("#element1");
rectangle_proto->add_elements()->add_selectors("#element2");
rectangle_proto->set_full_width(true);
element_area_.SetFromProto(area_proto);
std::vector<RectF> rectangles;
element_area_.GetRectangles(&rectangles);
EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.0f, 0.3f, 1.0f, 0.8f)));
}
TEST_F(ElementAreaTest, ElementMovesAfterUpdate) {
testing::InSequence seq;
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.25f, 1.0f, 0.5f)))
.WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.5f, 1.0f, 0.75f)));
SetElement("#element");
EXPECT_THAT(reported_area_,
ElementsAre(MatchingRectF(0.0f, 0.25f, 1.0f, 0.5f)));
element_area_.UpdatePositions();
// Updated area is available
std::vector<RectF> rectangles;
element_area_.GetRectangles(&rectangles);
EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.0f, 0.5f, 1.0f, 0.75f)));
// Updated area is reported
EXPECT_THAT(reported_area_,
ElementsAre(MatchingRectF(0.0f, 0.5f, 1.0f, 0.75f)));
}
TEST_F(ElementAreaTest, ElementMovesWithTime) {
testing::InSequence seq;
EXPECT_CALL(
mock_web_controller_,
OnGetElementPosition(Eq(Selector({"#element"}).MustBeVisible()), _))
.WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.25f, 1.0f, 0.5f)))
.WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.5f, 1.0f, 0.75f)));
SetElement("#element");
EXPECT_THAT(reported_area_,
ElementsAre(MatchingRectF(0.0f, 0.25f, 1.0f, 0.5f)));
scoped_task_environment_.FastForwardBy(
base::TimeDelta::FromMilliseconds(100));
// Updated area is available
std::vector<RectF> rectangles;
element_area_.GetRectangles(&rectangles);
EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.0f, 0.5f, 1.0f, 0.75f)));
// Updated area is reported
EXPECT_THAT(reported_area_,
ElementsAre(MatchingRectF(0.0f, 0.5f, 1.0f, 0.75f)));
}
} // namespace
} // namespace autofill_assistant