view_element_unittest.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/ui_devtools/views/view_element.h"
  5. #include <memory>
  6. #include "base/strings/utf_string_conversions.h"
  7. #include "components/ui_devtools/protocol.h"
  8. #include "components/ui_devtools/ui_devtools_unittest_utils.h"
  9. #include "ui/base/metadata/metadata_impl_macros.h"
  10. #include "ui/views/test/views_test_base.h"
  11. namespace ui_devtools {
  12. namespace {
  13. // Returns a pair of the ClassProperties index and UIProperty index
  14. std::pair<size_t, size_t> GetPropertyIndices(ui_devtools::ViewElement* element,
  15. const std::string& property_name) {
  16. std::vector<UIElement::ClassProperties> props =
  17. element->GetCustomPropertiesForMatchedStyle();
  18. for (size_t cp_index = 0; cp_index < props.size(); ++cp_index) {
  19. for (size_t uip_index = 0; uip_index < props[cp_index].properties_.size();
  20. ++uip_index) {
  21. if (props[cp_index].properties_[uip_index].name_ == property_name)
  22. return std::make_pair(cp_index, uip_index);
  23. }
  24. }
  25. DCHECK(false) << "Property " << property_name << " can not be found.";
  26. return std::make_pair(-1, -1);
  27. }
  28. void TestBooleanCustomPropertySetting(ui_devtools::ViewElement* element,
  29. const std::string& property_name,
  30. bool init_value) {
  31. std::pair<size_t, size_t> indices =
  32. GetPropertyIndices(element, property_name);
  33. std::string old_value(init_value ? "true" : "false");
  34. std::vector<UIElement::ClassProperties> props =
  35. element->GetCustomPropertiesForMatchedStyle();
  36. std::vector<UIElement::UIProperty> ui_props =
  37. props[indices.first].properties_;
  38. EXPECT_EQ(ui_props[indices.second].value_, old_value);
  39. // Check the property can be set accordingly.
  40. std::string new_value(init_value ? "false" : "true");
  41. std::string separator(":");
  42. element->SetPropertiesFromString(property_name + separator + new_value);
  43. props = element->GetCustomPropertiesForMatchedStyle();
  44. ui_props = props[indices.first].properties_;
  45. EXPECT_EQ(ui_props[indices.second].name_, property_name);
  46. EXPECT_EQ(ui_props[indices.second].value_, new_value);
  47. element->SetPropertiesFromString(property_name + separator + old_value);
  48. props = element->GetCustomPropertiesForMatchedStyle();
  49. ui_props = props[indices.first].properties_;
  50. EXPECT_EQ(ui_props[indices.second].name_, property_name);
  51. EXPECT_EQ(ui_props[indices.second].value_, old_value);
  52. }
  53. } // namespace
  54. using ::testing::_;
  55. class MockNamedTestView : public views::View {
  56. public:
  57. METADATA_HEADER(MockNamedTestView);
  58. // For custom properties test.
  59. std::u16string GetTooltipText(const gfx::Point& p) const override {
  60. return u"This is the tooltip";
  61. }
  62. int GetBoolProperty() const { return bool_property_; }
  63. void SetBoolProperty(bool bool_property) { bool_property_ = bool_property; }
  64. SkColor GetColorProperty() const { return color_property_; }
  65. void SetColorProperty(SkColor color) { color_property_ = color; }
  66. MOCK_METHOD1(OnMousePressed, bool(const ui::MouseEvent&));
  67. MOCK_METHOD1(OnMouseDragged, bool(const ui::MouseEvent&));
  68. MOCK_METHOD1(OnMouseReleased, void(const ui::MouseEvent&));
  69. MOCK_METHOD1(OnMouseMoved, void(const ui::MouseEvent&));
  70. MOCK_METHOD1(OnMouseEntered, void(const ui::MouseEvent&));
  71. MOCK_METHOD1(OnMouseExited, void(const ui::MouseEvent&));
  72. MOCK_METHOD1(OnMouseWheel, bool(const ui::MouseWheelEvent&));
  73. private:
  74. bool bool_property_ = false;
  75. SkColor color_property_ = SK_ColorGRAY;
  76. };
  77. BEGIN_METADATA(MockNamedTestView, views::View)
  78. ADD_PROPERTY_METADATA(bool, BoolProperty)
  79. ADD_PROPERTY_METADATA(SkColor, ColorProperty, ui::metadata::SkColorConverter)
  80. END_METADATA
  81. class AlwaysOnTopView : public views::View {
  82. METADATA_HEADER(AlwaysOnTopView);
  83. };
  84. BEGIN_METADATA(AlwaysOnTopView, views::View)
  85. END_METADATA
  86. class SelfReorderingTestView : public views::View, public views::ViewObserver {
  87. public:
  88. METADATA_HEADER(SelfReorderingTestView);
  89. SelfReorderingTestView()
  90. : always_on_top_view_(AddChildView(std::make_unique<AlwaysOnTopView>())) {
  91. AddObserver(this);
  92. }
  93. ~SelfReorderingTestView() override { RemoveObserver(this); }
  94. // views::ViewObserver
  95. void OnChildViewAdded(View* observed_view, View* child) override {
  96. ReorderChildView(always_on_top_view_, children().size());
  97. }
  98. private:
  99. raw_ptr<views::View> always_on_top_view_;
  100. };
  101. BEGIN_METADATA(SelfReorderingTestView, views::View)
  102. END_METADATA
  103. class ViewElementTest : public views::ViewsTestBase {
  104. public:
  105. ViewElementTest() {}
  106. ViewElementTest(const ViewElementTest&) = delete;
  107. ViewElementTest& operator=(const ViewElementTest&) = delete;
  108. ~ViewElementTest() override {}
  109. protected:
  110. void SetUp() override {
  111. views::ViewsTestBase::SetUp();
  112. view_ = std::make_unique<testing::NiceMock<MockNamedTestView>>();
  113. delegate_ = std::make_unique<testing::NiceMock<MockUIElementDelegate>>();
  114. // |OnUIElementAdded| is called on element creation.
  115. EXPECT_CALL(*delegate_, OnUIElementAdded(_, _)).Times(1);
  116. element_ =
  117. std::make_unique<ViewElement>(view_.get(), delegate_.get(), nullptr);
  118. }
  119. MockNamedTestView* view() { return view_.get(); }
  120. ViewElement* element() { return element_.get(); }
  121. MockUIElementDelegate* delegate() { return delegate_.get(); }
  122. private:
  123. std::unique_ptr<MockNamedTestView> view_;
  124. std::unique_ptr<ViewElement> element_;
  125. std::unique_ptr<MockUIElementDelegate> delegate_;
  126. };
  127. TEST_F(ViewElementTest, SettingsBoundsOnViewCallsDelegate) {
  128. EXPECT_CALL(*delegate(), OnUIElementBoundsChanged(element())).Times(1);
  129. view()->SetBounds(1, 2, 3, 4);
  130. }
  131. TEST_F(ViewElementTest, AddingChildView) {
  132. // The first call is from the element being created, before it
  133. // gets parented to |element_|.
  134. EXPECT_CALL(*delegate(), OnUIElementAdded(nullptr, _)).Times(1);
  135. EXPECT_CALL(*delegate(), OnUIElementAdded(element(), _)).Times(1);
  136. views::View child_view;
  137. view()->AddChildView(&child_view);
  138. DCHECK_EQ(element()->children().size(), 1U);
  139. UIElement* child_element = element()->children()[0];
  140. EXPECT_CALL(*delegate(), OnUIElementRemoved(child_element)).Times(1);
  141. view()->RemoveChildView(&child_view);
  142. }
  143. TEST_F(ViewElementTest, SettingsBoundsOnElementSetsOnView) {
  144. DCHECK(view()->bounds() == gfx::Rect());
  145. element()->SetBounds(gfx::Rect(1, 2, 3, 4));
  146. EXPECT_EQ(view()->bounds(), gfx::Rect(1, 2, 3, 4));
  147. }
  148. TEST_F(ViewElementTest, SetPropertiesFromString) {
  149. static const char* kEnabledProperty = "Enabled";
  150. TestBooleanCustomPropertySetting(element(), kEnabledProperty, true);
  151. std::pair<size_t, size_t> indices =
  152. GetPropertyIndices(element(), kEnabledProperty);
  153. // Test setting a non-existent property has no effect.
  154. element()->SetPropertiesFromString("Enable:false");
  155. std::vector<UIElement::ClassProperties> props =
  156. element()->GetCustomPropertiesForMatchedStyle();
  157. std::vector<UIElement::UIProperty> ui_props =
  158. props[indices.first].properties_;
  159. EXPECT_EQ(ui_props[indices.second].name_, kEnabledProperty);
  160. EXPECT_EQ(ui_props[indices.second].value_, "true");
  161. // Test setting empty string for property value has no effect.
  162. element()->SetPropertiesFromString("Enabled:");
  163. props = element()->GetCustomPropertiesForMatchedStyle();
  164. ui_props = props[indices.first].properties_;
  165. EXPECT_EQ(ui_props[indices.second].name_, kEnabledProperty);
  166. EXPECT_EQ(ui_props[indices.second].value_, "true");
  167. // Ensure setting pure whitespace doesn't crash.
  168. ASSERT_NO_FATAL_FAILURE(element()->SetPropertiesFromString(" \n "));
  169. }
  170. TEST_F(ViewElementTest, SettingVisibilityOnView) {
  171. TestBooleanCustomPropertySetting(element(), "Visible", true);
  172. }
  173. TEST_F(ViewElementTest, SettingSubclassBoolProperty) {
  174. TestBooleanCustomPropertySetting(element(), "BoolProperty", false);
  175. }
  176. TEST_F(ViewElementTest, GetBounds) {
  177. gfx::Rect bounds;
  178. view()->SetBounds(10, 20, 30, 40);
  179. element()->GetBounds(&bounds);
  180. EXPECT_EQ(bounds, gfx::Rect(10, 20, 30, 40));
  181. }
  182. TEST_F(ViewElementTest, GetAttributes) {
  183. std::vector<std::string> attrs = element()->GetAttributes();
  184. EXPECT_THAT(attrs,
  185. testing::ElementsAre("name", MockNamedTestView::kViewClassName));
  186. }
  187. TEST_F(ViewElementTest, GetCustomProperties) {
  188. std::vector<UIElement::ClassProperties> props =
  189. element()->GetCustomPropertiesForMatchedStyle();
  190. // The ClassProperties vector should be of size 2.
  191. DCHECK_EQ(props.size(), 2U);
  192. std::pair<size_t, size_t> indices =
  193. GetPropertyIndices(element(), "BoolProperty");
  194. // The BoolProperty property should be in the second ClassProperties object in
  195. // the vector.
  196. DCHECK_EQ(indices.first, 0U);
  197. indices = GetPropertyIndices(element(), "Tooltip");
  198. // The tooltip property should be in the second ClassProperties object in the
  199. // vector.
  200. DCHECK_EQ(indices.first, 1U);
  201. std::vector<UIElement::UIProperty> ui_props =
  202. props[indices.first].properties_;
  203. EXPECT_EQ(ui_props[indices.second].name_, "Tooltip");
  204. EXPECT_EQ(ui_props[indices.second].value_, "This is the tooltip");
  205. }
  206. TEST_F(ViewElementTest, CheckCustomProperties) {
  207. std::vector<UIElement::ClassProperties> props =
  208. element()->GetCustomPropertiesForMatchedStyle();
  209. DCHECK_GT(props.size(), 1U);
  210. DCHECK_GT(props[1].properties_.size(), 1U);
  211. // Check visibility information is passed in.
  212. bool is_visible_set = false;
  213. for (size_t i = 0; i < props[1].properties_.size(); ++i) {
  214. if (props[1].properties_[i].name_ == "Visible")
  215. is_visible_set = true;
  216. }
  217. EXPECT_TRUE(is_visible_set);
  218. }
  219. TEST_F(ViewElementTest, GetNodeWindowAndScreenBounds) {
  220. // For this to be meaningful, the view must be in
  221. // a widget.
  222. auto widget = std::make_unique<views::Widget>();
  223. views::Widget::InitParams params =
  224. CreateParams(views::Widget::InitParams::TYPE_WINDOW);
  225. params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
  226. widget->Init(std::move(params));
  227. widget->Show();
  228. widget->GetContentsView()->AddChildView(view());
  229. gfx::Rect bounds(50, 60, 70, 80);
  230. view()->SetBoundsRect(bounds);
  231. std::pair<gfx::NativeWindow, gfx::Rect> window_and_bounds =
  232. element()->GetNodeWindowAndScreenBounds();
  233. EXPECT_EQ(window_and_bounds.first, widget->GetNativeWindow());
  234. EXPECT_EQ(window_and_bounds.second, view()->GetBoundsInScreen());
  235. view()->parent()->RemoveChildView(view());
  236. }
  237. TEST_F(ViewElementTest, ColorProperty) {
  238. EXPECT_EQ(GetPropertyIndices(element(), "--ColorProperty").first, 0U);
  239. DCHECK_EQ(view()->GetColorProperty(), SK_ColorGRAY);
  240. EXPECT_TRUE(element()->SetPropertiesFromString(
  241. "--ColorProperty: rgba(0,0, 255, 1);"));
  242. EXPECT_EQ(view()->GetColorProperty(), SK_ColorBLUE);
  243. EXPECT_TRUE(element()->SetPropertiesFromString(
  244. "--ColorProperty: hsl(240, 84%, 28%);"));
  245. EXPECT_EQ(view()->GetColorProperty(), SkColorSetARGB(255, 0x0B, 0x0B, 0x47));
  246. EXPECT_TRUE(element()->SetPropertiesFromString(
  247. "--ColorProperty: hsla(240, 84%, 28%, 0.5);"));
  248. EXPECT_EQ(view()->GetColorProperty(), SkColorSetARGB(128, 0x0B, 0x0B, 0x47));
  249. }
  250. TEST_F(ViewElementTest, BadColorProperty) {
  251. DCHECK_EQ(view()->GetColorProperty(), SK_ColorGRAY);
  252. element()->SetPropertiesFromString("--ColorProperty: #0352fc");
  253. EXPECT_EQ(view()->GetColorProperty(), SK_ColorGRAY);
  254. element()->SetPropertiesFromString("--ColorProperty: rgba(1,2,3,4);");
  255. EXPECT_EQ(view()->GetColorProperty(), SK_ColorGRAY);
  256. element()->SetPropertiesFromString("--ColorProperty: rgba(1,2,3,4;");
  257. EXPECT_EQ(view()->GetColorProperty(), SK_ColorGRAY);
  258. element()->SetPropertiesFromString("--ColorProperty: rgb(1,2,3,4;)");
  259. EXPECT_EQ(view()->GetColorProperty(), SK_ColorGRAY);
  260. }
  261. TEST_F(ViewElementTest, GetSources) {
  262. std::vector<UIElement::Source> sources = element()->GetSources();
  263. // ViewElement should have two sources: from MockNamedTestView and from View.
  264. EXPECT_EQ(sources.size(), 2U);
  265. #if defined(__clang__) && defined(_MSC_VER)
  266. EXPECT_EQ(sources[0].path_,
  267. "components\\ui_devtools\\views\\view_element_unittest.cc");
  268. EXPECT_EQ(sources[1].path_, "ui\\views\\view.h");
  269. #else
  270. EXPECT_EQ(sources[0].path_,
  271. "components/ui_devtools/views/view_element_unittest.cc");
  272. EXPECT_EQ(sources[1].path_, "ui/views/view.h");
  273. #endif
  274. }
  275. TEST_F(ViewElementTest, DispatchMouseEvent) {
  276. // The view must be in a widget in order to dispatch mouse event correctly.
  277. auto widget = std::make_unique<views::Widget>();
  278. views::Widget::InitParams params =
  279. CreateParams(views::Widget::InitParams::TYPE_WINDOW);
  280. params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
  281. widget->Init(std::move(params));
  282. widget->GetContentsView()->AddChildView(view());
  283. widget->Show();
  284. gfx::Rect bounds(50, 60, 70, 80);
  285. view()->SetBoundsRect(bounds);
  286. std::pair<gfx::NativeWindow, gfx::Rect> window_and_bounds =
  287. element()->GetNodeWindowAndScreenBounds();
  288. EXPECT_EQ(window_and_bounds.first, widget->GetNativeWindow());
  289. EXPECT_EQ(window_and_bounds.second, view()->GetBoundsInScreen());
  290. EXPECT_CALL(*view(), OnMousePressed(_)).Times(1);
  291. EXPECT_CALL(*view(), OnMouseDragged(_)).Times(1);
  292. EXPECT_CALL(*view(), OnMouseReleased(_)).Times(1);
  293. EXPECT_CALL(*view(), OnMouseMoved(_)).Times(1);
  294. EXPECT_CALL(*view(), OnMouseEntered(_)).Times(1);
  295. EXPECT_CALL(*view(), OnMouseExited(_)).Times(1);
  296. EXPECT_CALL(*view(), OnMouseWheel(_)).Times(1);
  297. std::vector<std::unique_ptr<protocol::DOM::MouseEvent>> events;
  298. events.emplace_back(
  299. protocol::DOM::MouseEvent::create()
  300. .setType(protocol::DOM::MouseEvent::TypeEnum::MousePressed)
  301. .setX(0)
  302. .setY(0)
  303. .setButton(protocol::DOM::MouseEvent::ButtonEnum::Left)
  304. .setWheelDirection(
  305. protocol::DOM::MouseEvent::WheelDirectionEnum::None)
  306. .build());
  307. events.emplace_back(
  308. protocol::DOM::MouseEvent::create()
  309. .setType(protocol::DOM::MouseEvent::TypeEnum::MouseDragged)
  310. .setX(0)
  311. .setY(0)
  312. .setButton(protocol::DOM::MouseEvent::ButtonEnum::Left)
  313. .setWheelDirection(
  314. protocol::DOM::MouseEvent::WheelDirectionEnum::None)
  315. .build());
  316. events.emplace_back(
  317. protocol::DOM::MouseEvent::create()
  318. .setType(protocol::DOM::MouseEvent::TypeEnum::MouseReleased)
  319. .setX(0)
  320. .setY(0)
  321. .setButton(protocol::DOM::MouseEvent::ButtonEnum::Left)
  322. .setWheelDirection(
  323. protocol::DOM::MouseEvent::WheelDirectionEnum::None)
  324. .build());
  325. events.emplace_back(
  326. protocol::DOM::MouseEvent::create()
  327. .setType(protocol::DOM::MouseEvent::TypeEnum::MouseMoved)
  328. .setX(0)
  329. .setY(0)
  330. .setButton(protocol::DOM::MouseEvent::ButtonEnum::None)
  331. .setWheelDirection(
  332. protocol::DOM::MouseEvent::WheelDirectionEnum::None)
  333. .build());
  334. events.emplace_back(
  335. protocol::DOM::MouseEvent::create()
  336. .setType(protocol::DOM::MouseEvent::TypeEnum::MouseEntered)
  337. .setX(0)
  338. .setY(0)
  339. .setButton(protocol::DOM::MouseEvent::ButtonEnum::None)
  340. .setWheelDirection(
  341. protocol::DOM::MouseEvent::WheelDirectionEnum::None)
  342. .build());
  343. events.emplace_back(
  344. protocol::DOM::MouseEvent::create()
  345. .setType(protocol::DOM::MouseEvent::TypeEnum::MouseExited)
  346. .setX(0)
  347. .setY(0)
  348. .setButton(protocol::DOM::MouseEvent::ButtonEnum::None)
  349. .setWheelDirection(
  350. protocol::DOM::MouseEvent::WheelDirectionEnum::None)
  351. .build());
  352. events.emplace_back(
  353. protocol::DOM::MouseEvent::create()
  354. .setType(protocol::DOM::MouseEvent::TypeEnum::MouseWheel)
  355. .setX(0)
  356. .setY(0)
  357. .setButton(protocol::DOM::MouseEvent::ButtonEnum::None)
  358. .setWheelDirection(protocol::DOM::MouseEvent::WheelDirectionEnum::Up)
  359. .build());
  360. for (auto& event : events)
  361. element()->DispatchMouseEvent(event.get());
  362. view()->parent()->RemoveChildView(view());
  363. }
  364. TEST_F(ViewElementTest, OutOfOrderObserverTest) {
  365. // Override the expectation from setup; we don't need to keep track
  366. // in this test.
  367. EXPECT_CALL(*delegate(), OnUIElementAdded(_, _)).Times(testing::AnyNumber());
  368. auto view = std::make_unique<SelfReorderingTestView>();
  369. auto element = std::make_unique<ViewElement>(view.get(), delegate(), nullptr);
  370. // `element` will receive OnChildViewReordered and OnViewAdded out of
  371. // order. Ensure it doesn't crash and that the subtree is consistent
  372. // afterward.
  373. view->AddChildView(std::make_unique<views::View>());
  374. ASSERT_EQ(element->children().size(), 2u);
  375. auto attrs = element->children().at(1)->GetAttributes();
  376. EXPECT_EQ(attrs[0], "name");
  377. std::string& name = attrs[1];
  378. EXPECT_EQ(name, "AlwaysOnTopView");
  379. }
  380. } // namespace ui_devtools