ax_tree_converter_unittest.cc 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. // Copyright 2019 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 "fuchsia_web/webengine/browser/ax_tree_converter.h"
  5. #include <lib/ui/scenic/cpp/commands.h>
  6. #include <vector>
  7. #include "testing/gtest/include/gtest/gtest.h"
  8. #include "ui/accessibility/ax_tree.h"
  9. #include "ui/gfx/geometry/transform.h"
  10. namespace {
  11. using fuchsia::accessibility::semantics::Action;
  12. using fuchsia::accessibility::semantics::Attributes;
  13. using fuchsia::accessibility::semantics::CheckedState;
  14. using fuchsia::accessibility::semantics::Node;
  15. using fuchsia::accessibility::semantics::Role;
  16. using fuchsia::accessibility::semantics::States;
  17. const char kLabel1[] = "label nodes, not people";
  18. const char kLabel2[] = "fancy stickers";
  19. const char kDescription1[] = "this node does some stuff";
  20. const char kValue1[] = "user entered value";
  21. const int32_t kRootId = 182;
  22. const int32_t kChildId1 = 23901;
  23. const int32_t kChildId2 = 484345;
  24. const int32_t kChildId3 = 4156877;
  25. const int32_t kChildId4 = 45877;
  26. const int32_t kRowNodeId1 = 2;
  27. const int32_t kRowNodeId2 = 3;
  28. const int32_t kCellNodeId = 7;
  29. const int32_t kRectX = 1;
  30. const int32_t kRectY = 2;
  31. const int32_t kRectWidth = 7;
  32. const int32_t kRectHeight = 8;
  33. const std::array<float, 16> k4DIdentityMatrixWithDefaultOffset = {
  34. 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 2, 0, 1};
  35. class MockNodeIDMapper : public NodeIDMapper {
  36. public:
  37. MockNodeIDMapper() = default;
  38. ~MockNodeIDMapper() override = default;
  39. uint32_t ToFuchsiaNodeID(const ui::AXTreeID& ax_tree_id,
  40. int32_t ax_node_id,
  41. bool is_tree_root) override {
  42. return base::checked_cast<uint32_t>(ax_node_id);
  43. }
  44. };
  45. ui::AXNodeData CreateAXNodeData(ax::mojom::Role role,
  46. ax::mojom::Action action,
  47. std::vector<int32_t> child_ids,
  48. ui::AXRelativeBounds relative_bounds,
  49. base::StringPiece name,
  50. base::StringPiece description,
  51. ax::mojom::CheckedState checked_state) {
  52. ui::AXNodeData node;
  53. node.id = 2;
  54. node.role = role;
  55. if (action != ax::mojom::Action::kNone) {
  56. node.AddAction(action);
  57. }
  58. node.AddIntAttribute(ax::mojom::IntAttribute::kCheckedState,
  59. static_cast<int32_t>(checked_state));
  60. node.child_ids = child_ids;
  61. node.relative_bounds = relative_bounds;
  62. if (!name.empty())
  63. node.AddStringAttribute(ax::mojom::StringAttribute::kName, name.data());
  64. if (!description.empty()) {
  65. node.AddStringAttribute(ax::mojom::StringAttribute::kDescription,
  66. description.data());
  67. }
  68. return node;
  69. }
  70. Node CreateSemanticNode(uint32_t id,
  71. Role role,
  72. Attributes attributes,
  73. States states,
  74. std::vector<Action> actions,
  75. std::vector<uint32_t> child_ids,
  76. fuchsia::ui::gfx::BoundingBox location,
  77. fuchsia::ui::gfx::mat4 transform) {
  78. Node node;
  79. node.set_node_id(id);
  80. node.set_role(role);
  81. node.set_attributes(std::move(attributes));
  82. node.set_states(std::move(states));
  83. node.set_actions(actions);
  84. node.set_child_ids(child_ids);
  85. node.set_location(location);
  86. node.set_transform(transform);
  87. node.set_container_id(kRootId);
  88. return node;
  89. }
  90. // Create an AXNodeData and a Fuchsia node that represent the same information.
  91. std::pair<ui::AXNodeData, Node> CreateSemanticNodeAllFieldsSet() {
  92. ui::AXRelativeBounds relative_bounds = ui::AXRelativeBounds();
  93. relative_bounds.bounds = gfx::RectF(kRectX, kRectY, kRectWidth, kRectHeight);
  94. relative_bounds.transform =
  95. std::make_unique<gfx::Transform>(gfx::Transform::kSkipInitialization);
  96. relative_bounds.transform->MakeIdentity();
  97. relative_bounds.offset_container_id = -1;
  98. auto ax_node_data = CreateAXNodeData(
  99. ax::mojom::Role::kButton, ax::mojom::Action::kFocus,
  100. std::vector<int32_t>{kChildId1, kChildId2, kChildId3}, relative_bounds,
  101. kLabel1, kDescription1, ax::mojom::CheckedState::kMixed);
  102. ax_node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
  103. ax_node_data.AddIntAttribute(ax::mojom::IntAttribute::kScrollX, 10);
  104. ax_node_data.RemoveState(ax::mojom::State::kIgnored);
  105. ax_node_data.AddState(ax::mojom::State::kFocusable);
  106. ax_node_data.AddIntAttribute(ax::mojom::IntAttribute::kScrollY, 20);
  107. ax_node_data.id = kChildId4;
  108. Attributes attributes;
  109. attributes.set_label(kLabel1);
  110. attributes.set_secondary_label(kDescription1);
  111. fuchsia::ui::gfx::BoundingBox box;
  112. box.min = scenic::NewVector3({kRectX, kRectY, 0.0f});
  113. box.max =
  114. scenic::NewVector3({kRectX + kRectWidth, kRectY + kRectHeight, 0.0f});
  115. fuchsia::ui::gfx::Matrix4Value mat =
  116. scenic::NewMatrix4Value(k4DIdentityMatrixWithDefaultOffset);
  117. States states;
  118. states.set_checked_state(CheckedState::MIXED);
  119. states.set_hidden(false);
  120. states.set_selected(false);
  121. states.set_viewport_offset({10, 20});
  122. states.set_focusable(true);
  123. MockNodeIDMapper mapper;
  124. auto fuchsia_node = CreateSemanticNode(
  125. mapper.ToFuchsiaNodeID(ui::AXTreeID::CreateNewAXTreeID(), ax_node_data.id,
  126. false),
  127. Role::BUTTON, std::move(attributes), std::move(states),
  128. std::vector<Action>{Action::SET_FOCUS},
  129. std::vector<uint32_t>{kChildId1, kChildId2, kChildId3}, box, mat.value);
  130. return std::make_pair(std::move(ax_node_data), std::move(fuchsia_node));
  131. }
  132. class AXTreeConverterTest : public testing::Test {
  133. public:
  134. AXTreeConverterTest() {
  135. ui::AXRelativeBounds relative_bounds = ui::AXRelativeBounds();
  136. relative_bounds.bounds =
  137. gfx::RectF(kRectX, kRectY, kRectWidth, kRectHeight);
  138. root_node_data_ =
  139. CreateAXNodeData(ax::mojom::Role::kNone, ax::mojom::Action::kNone,
  140. std::vector<int32_t>{}, relative_bounds, "", "",
  141. ax::mojom::CheckedState::kNone);
  142. root_node_data_.id = kRootId;
  143. ui::AXTreeUpdate initial_state;
  144. initial_state.root_id = root_node_data_.id;
  145. initial_state.nodes = {root_node_data_};
  146. initial_state.has_tree_data = true;
  147. ui::AXTreeData tree_data;
  148. tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
  149. tree_data.title = "test";
  150. initial_state.tree_data = tree_data;
  151. EXPECT_TRUE(tree_.Unserialize(initial_state)) << tree_.error();
  152. }
  153. AXTreeConverterTest(const AXTreeConverterTest&) = delete;
  154. AXTreeConverterTest& operator=(const AXTreeConverterTest&) = delete;
  155. ~AXTreeConverterTest() override = default;
  156. ui::AXNode& root_node() { return *tree_.root(); }
  157. // Adds |node_data| as a child of the root of the test tree. Returns a
  158. // reference to the newly added node.
  159. ui::AXNode& AddChildNode(const ui::AXNodeData& node_data) {
  160. ui::AXTreeUpdate update;
  161. update.root_id = root_node_data_.id;
  162. root_node_data_.child_ids = {node_data.id};
  163. update.nodes = {root_node_data_, node_data};
  164. // Checks if the node being added has children. If so, add dummy nodes to
  165. // represent them so the resulting tree is valid.
  166. if (!node_data.child_ids.empty()) {
  167. for (auto& id : node_data.child_ids) {
  168. ui::AXNodeData child;
  169. child.id = id;
  170. update.nodes.push_back(child);
  171. }
  172. }
  173. EXPECT_TRUE(tree_.Unserialize(update)) << tree_.error();
  174. auto* ax_node = tree_.GetFromId(node_data.id);
  175. EXPECT_TRUE(ax_node);
  176. return *ax_node;
  177. }
  178. // Helper method to create a table in |tree_|.
  179. // This is a simple 2 x 2 table with 2 column headers in first row, 2 cells in
  180. // second row. The first row is parented by a rowgroup.
  181. void CreateTableForTest() {
  182. ui::AXTreeUpdate update;
  183. update.root_id = kRootId;
  184. update.nodes.resize(8);
  185. auto& table = update.nodes[0];
  186. table.id = kRootId;
  187. table.role = ax::mojom::Role::kTable;
  188. table.AddIntAttribute(ax::mojom::IntAttribute::kTableRowCount, 2);
  189. table.AddIntAttribute(ax::mojom::IntAttribute::kTableColumnCount, 2);
  190. table.child_ids = {888, kRowNodeId2};
  191. auto& row_group = update.nodes[1];
  192. row_group.id = 888;
  193. row_group.role = ax::mojom::Role::kRowGroup;
  194. row_group.child_ids = {kRowNodeId1};
  195. auto& row_1 = update.nodes[2];
  196. row_1.id = kRowNodeId1;
  197. row_1.role = ax::mojom::Role::kRow;
  198. row_1.AddIntAttribute(ax::mojom::IntAttribute::kTableRowIndex, 0);
  199. row_1.child_ids = {4, 5};
  200. auto& row_2 = update.nodes[3];
  201. row_2.id = kRowNodeId2;
  202. row_2.role = ax::mojom::Role::kRow;
  203. row_2.AddIntAttribute(ax::mojom::IntAttribute::kTableRowIndex, 1);
  204. row_2.child_ids = {6, kCellNodeId};
  205. auto& column_header_1 = update.nodes[4];
  206. column_header_1.id = 4;
  207. column_header_1.role = ax::mojom::Role::kColumnHeader;
  208. column_header_1.AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex,
  209. 0);
  210. column_header_1.AddIntAttribute(
  211. ax::mojom::IntAttribute::kTableCellColumnIndex, 0);
  212. auto& column_header_2 = update.nodes[5];
  213. column_header_2.id = 5;
  214. column_header_2.role = ax::mojom::Role::kColumnHeader;
  215. column_header_2.AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex,
  216. 0);
  217. column_header_2.AddIntAttribute(
  218. ax::mojom::IntAttribute::kTableCellColumnIndex, 1);
  219. auto& cell_1 = update.nodes[6];
  220. cell_1.id = 6;
  221. cell_1.role = ax::mojom::Role::kCell;
  222. cell_1.AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex, 1);
  223. cell_1.AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnIndex, 0);
  224. auto& cell_2 = update.nodes[7];
  225. cell_2.id = kCellNodeId;
  226. cell_2.role = ax::mojom::Role::kCell;
  227. cell_2.AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex, 1);
  228. cell_2.AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnIndex, 1);
  229. EXPECT_TRUE(tree_.Unserialize(update)) << tree_.error();
  230. }
  231. private:
  232. ui::AXNodeData root_node_data_;
  233. ui::AXTree tree_;
  234. };
  235. TEST_F(AXTreeConverterTest, AllFieldsSetAndEqual) {
  236. auto nodes = CreateSemanticNodeAllFieldsSet();
  237. auto& source_node_data = nodes.first;
  238. auto& expected_node = nodes.second;
  239. MockNodeIDMapper mapper;
  240. auto converted_node = AXNodeDataToSemanticNode(
  241. AddChildNode(source_node_data), root_node(),
  242. ui::AXTreeID::CreateNewAXTreeID(), false, 0.0f, &mapper);
  243. EXPECT_TRUE(fidl::Equals(converted_node, expected_node));
  244. }
  245. TEST_F(AXTreeConverterTest, TransformAccountsForContainerOffset) {
  246. ui::AXNodeData child_node_data;
  247. child_node_data.id = 1;
  248. child_node_data.relative_bounds.transform = std::make_unique<gfx::Transform>(
  249. 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
  250. auto& child_node = AddChildNode(child_node_data);
  251. root_node().SetLocation(
  252. kRootId,
  253. gfx::RectF(100 /* x */, 200 /* y */, 10 /* width */, 20 /* height */),
  254. nullptr);
  255. MockNodeIDMapper mapper;
  256. auto converted_node = AXNodeDataToSemanticNode(
  257. child_node, root_node(), ui::AXTreeID::CreateNewAXTreeID(), false, 0.0f,
  258. &mapper);
  259. Node expected_node;
  260. expected_node.set_node_id(1);
  261. fuchsia::ui::gfx::BoundingBox box;
  262. expected_node.set_location(std::move(box));
  263. // The fuchsia node transform should include a post-translation for the
  264. // container node's relative bounds.
  265. auto expected_transform = scenic::NewMatrix4Value(
  266. {2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 100, 200, 0, 1});
  267. expected_node.set_transform(expected_transform.value);
  268. EXPECT_EQ(converted_node.transform().matrix,
  269. expected_node.transform().matrix);
  270. }
  271. TEST_F(AXTreeConverterTest, SomeFieldsSetAndEqual) {
  272. ui::AXNodeData source_node_data;
  273. source_node_data.id = 0;
  274. source_node_data.AddAction(ax::mojom::Action::kFocus);
  275. source_node_data.AddAction(ax::mojom::Action::kSetValue);
  276. source_node_data.child_ids = std::vector<int32_t>{kChildId1};
  277. source_node_data.role = ax::mojom::Role::kImage;
  278. source_node_data.AddStringAttribute(ax::mojom::StringAttribute::kValue,
  279. kValue1);
  280. MockNodeIDMapper mapper;
  281. auto converted_node = AXNodeDataToSemanticNode(
  282. AddChildNode(source_node_data), root_node(),
  283. ui::AXTreeID::CreateNewAXTreeID(), false, 0.0f, &mapper);
  284. Node expected_node;
  285. expected_node.set_node_id(0);
  286. expected_node.set_actions(
  287. std::vector<Action>{Action::SET_FOCUS, Action::SET_VALUE});
  288. expected_node.set_child_ids(std::vector<uint32_t>{kChildId1});
  289. expected_node.set_role(Role::IMAGE);
  290. States states;
  291. states.set_hidden(false);
  292. states.set_value(kValue1);
  293. expected_node.set_states(std::move(states));
  294. Attributes attributes;
  295. expected_node.set_attributes(std::move(attributes));
  296. fuchsia::ui::gfx::BoundingBox box;
  297. expected_node.set_location(std::move(box));
  298. expected_node.set_container_id(kRootId);
  299. fuchsia::ui::gfx::Matrix4Value mat =
  300. scenic::NewMatrix4Value(k4DIdentityMatrixWithDefaultOffset);
  301. expected_node.set_transform(mat.value);
  302. EXPECT_TRUE(fidl::Equals(converted_node, expected_node));
  303. }
  304. TEST_F(AXTreeConverterTest, FieldMismatch) {
  305. ui::AXRelativeBounds relative_bounds = ui::AXRelativeBounds();
  306. relative_bounds.bounds = gfx::RectF(kRectX, kRectY, kRectWidth, kRectHeight);
  307. relative_bounds.transform =
  308. std::make_unique<gfx::Transform>(gfx::Transform::kSkipInitialization);
  309. relative_bounds.transform->MakeIdentity();
  310. auto source_node_data = CreateAXNodeData(
  311. ax::mojom::Role::kHeader, ax::mojom::Action::kSetValue,
  312. std::vector<int32_t>{kChildId1, kChildId2, kChildId3}, relative_bounds,
  313. kLabel1, kDescription1, ax::mojom::CheckedState::kFalse);
  314. MockNodeIDMapper mapper;
  315. auto converted_node = AXNodeDataToSemanticNode(
  316. AddChildNode(source_node_data), root_node(),
  317. ui::AXTreeID::CreateNewAXTreeID(), false, 0.0f, &mapper);
  318. Attributes attributes;
  319. attributes.set_label(kLabel1);
  320. attributes.set_secondary_label(kDescription1);
  321. States states;
  322. states.set_hidden(false);
  323. states.set_checked_state(CheckedState::UNCHECKED);
  324. fuchsia::ui::gfx::BoundingBox box;
  325. box.min = scenic::NewVector3({kRectX, kRectY, 0.0f});
  326. box.max =
  327. scenic::NewVector3({kRectX + kRectWidth, kRectY + kRectHeight, 0.0f});
  328. fuchsia::ui::gfx::Matrix4Value mat =
  329. scenic::NewMatrix4Value(k4DIdentityMatrixWithDefaultOffset);
  330. auto expected_node = CreateSemanticNode(
  331. source_node_data.id, Role::HEADER, std::move(attributes),
  332. std::move(states), std::vector<Action>{Action::SET_VALUE},
  333. std::vector<uint32_t>{kChildId1, kChildId2, kChildId3}, box, mat.value);
  334. // Start with nodes being equal.
  335. EXPECT_TRUE(fidl::Equals(converted_node, expected_node));
  336. // Make a copy of |source_node_data| and change the name attribute. Check that
  337. // the resulting |converted_node| is different from |expected_node|.
  338. auto modified_node_data = source_node_data;
  339. modified_node_data.AddStringAttribute(ax::mojom::StringAttribute::kName,
  340. kLabel2);
  341. converted_node = AXNodeDataToSemanticNode(
  342. AddChildNode(modified_node_data), root_node(),
  343. ui::AXTreeID::CreateNewAXTreeID(), false, 0.0f, &mapper);
  344. EXPECT_FALSE(fidl::Equals(converted_node, expected_node));
  345. // The same as above, this time changing |child_ids|.
  346. modified_node_data = source_node_data;
  347. modified_node_data.child_ids = std::vector<int32_t>{};
  348. converted_node = AXNodeDataToSemanticNode(
  349. AddChildNode(modified_node_data), root_node(),
  350. ui::AXTreeID::CreateNewAXTreeID(), false, 0.0f, &mapper);
  351. EXPECT_FALSE(fidl::Equals(converted_node, expected_node));
  352. }
  353. TEST_F(AXTreeConverterTest, LocationFieldRespectsTypeInvariants) {
  354. ui::AXRelativeBounds relative_bounds = ui::AXRelativeBounds();
  355. relative_bounds.bounds = gfx::RectF(kRectX, kRectY, kRectWidth, kRectHeight);
  356. relative_bounds.transform =
  357. std::make_unique<gfx::Transform>(gfx::Transform::kSkipInitialization);
  358. relative_bounds.transform->MakeIdentity();
  359. auto source_node_data = CreateAXNodeData(
  360. ax::mojom::Role::kHeader, ax::mojom::Action::kSetValue,
  361. std::vector<int32_t>{kChildId1, kChildId2, kChildId3}, relative_bounds,
  362. kLabel1, kDescription1, ax::mojom::CheckedState::kFalse);
  363. MockNodeIDMapper mapper;
  364. auto converted_node = AXNodeDataToSemanticNode(
  365. AddChildNode(source_node_data), root_node(),
  366. ui::AXTreeID::CreateNewAXTreeID(), false, 0.0f, &mapper);
  367. // The type definition of the location field requires that in order to be
  368. // interpreted as having non-zero length in a dimension, the min must be less
  369. // than the max in that dimension.
  370. EXPECT_LE(converted_node.location().min.x, converted_node.location().max.x);
  371. EXPECT_LE(converted_node.location().min.y, converted_node.location().max.y);
  372. EXPECT_LE(converted_node.location().min.z, converted_node.location().max.z);
  373. }
  374. TEST_F(AXTreeConverterTest, DefaultAction) {
  375. auto nodes = CreateSemanticNodeAllFieldsSet();
  376. auto& source_node_data = nodes.first;
  377. auto& expected_node = nodes.second;
  378. // Default action verb on an AXNodeData is equivalent to Action::DEFAULT on a
  379. // Fuchsia semantic node.
  380. source_node_data.SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kClick);
  381. expected_node.mutable_actions()->insert(
  382. expected_node.mutable_actions()->begin(),
  383. fuchsia::accessibility::semantics::Action::DEFAULT);
  384. MockNodeIDMapper mapper;
  385. auto converted_node = AXNodeDataToSemanticNode(
  386. AddChildNode(source_node_data), root_node(),
  387. ui::AXTreeID::CreateNewAXTreeID(), false, 0.0f, &mapper);
  388. EXPECT_TRUE(fidl::Equals(converted_node, expected_node));
  389. }
  390. TEST_F(AXTreeConverterTest, MapsNodeIDs) {
  391. NodeIDMapper mapper;
  392. const ui::AXTreeID tree_id_1 = ui::AXTreeID::CreateNewAXTreeID();
  393. const ui::AXTreeID tree_id_2 = ui::AXTreeID::CreateNewAXTreeID();
  394. const ui::AXTreeID tree_id_3 = ui::AXTreeID::CreateNewAXTreeID();
  395. auto id = mapper.ToFuchsiaNodeID(tree_id_1, 1, false);
  396. EXPECT_EQ(id, 1u);
  397. id = mapper.ToFuchsiaNodeID(tree_id_2, 1, false);
  398. EXPECT_EQ(id, 2u);
  399. const auto result_1 = mapper.ToAXNodeID(1u);
  400. EXPECT_TRUE(result_1);
  401. EXPECT_EQ(result_1->first, tree_id_1);
  402. EXPECT_EQ(result_1->second, 1);
  403. const auto result_2 = mapper.ToAXNodeID(2u);
  404. EXPECT_TRUE(result_2);
  405. EXPECT_EQ(result_2->first, tree_id_2);
  406. EXPECT_EQ(result_2->second, 1);
  407. // Set the root.
  408. id = mapper.ToFuchsiaNodeID(tree_id_1, 2, true);
  409. EXPECT_EQ(id, 0u);
  410. // Update the root. The old root should receive a new value.
  411. id = mapper.ToFuchsiaNodeID(tree_id_1, 1, true);
  412. EXPECT_EQ(id, 0u);
  413. const auto result_3 = mapper.ToAXNodeID(3u);
  414. EXPECT_TRUE(result_3);
  415. EXPECT_EQ(result_3->first, tree_id_1);
  416. EXPECT_EQ(result_3->second, 2); // First root's ID.
  417. mapper.UpdateAXTreeIDForCachedNodeIDs(tree_id_1, tree_id_3);
  418. const auto result_4 = mapper.ToAXNodeID(3u);
  419. EXPECT_TRUE(result_4);
  420. EXPECT_EQ(result_4->first, tree_id_3);
  421. EXPECT_EQ(result_4->second, 2);
  422. }
  423. TEST_F(AXTreeConverterTest, ConvertRoles) {
  424. MockNodeIDMapper mapper;
  425. ui::AXNodeData node;
  426. node.id = 0;
  427. node.role = ax::mojom::Role::kButton;
  428. EXPECT_EQ(fuchsia::accessibility::semantics::Role::BUTTON,
  429. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  430. ui::AXTreeID::CreateNewAXTreeID(), false,
  431. 0.0f, &mapper)
  432. .role());
  433. node.role = ax::mojom::Role::kCheckBox;
  434. EXPECT_EQ(fuchsia::accessibility::semantics::Role::CHECK_BOX,
  435. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  436. ui::AXTreeID::CreateNewAXTreeID(), false,
  437. 0.0f, &mapper)
  438. .role());
  439. node.role = ax::mojom::Role::kHeader;
  440. EXPECT_EQ(fuchsia::accessibility::semantics::Role::HEADER,
  441. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  442. ui::AXTreeID::CreateNewAXTreeID(), false,
  443. 0.0f, &mapper)
  444. .role());
  445. node.role = ax::mojom::Role::kImage;
  446. EXPECT_EQ(fuchsia::accessibility::semantics::Role::IMAGE,
  447. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  448. ui::AXTreeID::CreateNewAXTreeID(), false,
  449. 0.0f, &mapper)
  450. .role());
  451. node.role = ax::mojom::Role::kLink;
  452. EXPECT_EQ(fuchsia::accessibility::semantics::Role::LINK,
  453. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  454. ui::AXTreeID::CreateNewAXTreeID(), false,
  455. 0.0f, &mapper)
  456. .role());
  457. node.role = ax::mojom::Role::kRadioButton;
  458. EXPECT_EQ(fuchsia::accessibility::semantics::Role::RADIO_BUTTON,
  459. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  460. ui::AXTreeID::CreateNewAXTreeID(), false,
  461. 0.0f, &mapper)
  462. .role());
  463. node.role = ax::mojom::Role::kSlider;
  464. EXPECT_EQ(fuchsia::accessibility::semantics::Role::SLIDER,
  465. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  466. ui::AXTreeID::CreateNewAXTreeID(), false,
  467. 0.0f, &mapper)
  468. .role());
  469. node.role = ax::mojom::Role::kTextField;
  470. EXPECT_EQ(fuchsia::accessibility::semantics::Role::TEXT_FIELD,
  471. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  472. ui::AXTreeID::CreateNewAXTreeID(), false,
  473. 0.0f, &mapper)
  474. .role());
  475. node.role = ax::mojom::Role::kStaticText;
  476. EXPECT_EQ(fuchsia::accessibility::semantics::Role::STATIC_TEXT,
  477. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  478. ui::AXTreeID::CreateNewAXTreeID(), false,
  479. 0.0f, &mapper)
  480. .role());
  481. node.role = ax::mojom::Role::kSearchBox;
  482. EXPECT_EQ(fuchsia::accessibility::semantics::Role::SEARCH_BOX,
  483. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  484. ui::AXTreeID::CreateNewAXTreeID(), false,
  485. 0.0f, &mapper)
  486. .role());
  487. node.role = ax::mojom::Role::kTextFieldWithComboBox;
  488. EXPECT_EQ(fuchsia::accessibility::semantics::Role::TEXT_FIELD_WITH_COMBO_BOX,
  489. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  490. ui::AXTreeID::CreateNewAXTreeID(), false,
  491. 0.0f, &mapper)
  492. .role());
  493. node.role = ax::mojom::Role::kTable;
  494. EXPECT_EQ(fuchsia::accessibility::semantics::Role::TABLE,
  495. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  496. ui::AXTreeID::CreateNewAXTreeID(), false,
  497. 0.0f, &mapper)
  498. .role());
  499. node.role = ax::mojom::Role::kGrid;
  500. EXPECT_EQ(fuchsia::accessibility::semantics::Role::GRID,
  501. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  502. ui::AXTreeID::CreateNewAXTreeID(), false,
  503. 0.0f, &mapper)
  504. .role());
  505. node.role = ax::mojom::Role::kRow;
  506. EXPECT_EQ(fuchsia::accessibility::semantics::Role::TABLE_ROW,
  507. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  508. ui::AXTreeID::CreateNewAXTreeID(), false,
  509. 0.0f, &mapper)
  510. .role());
  511. node.role = ax::mojom::Role::kCell;
  512. EXPECT_EQ(fuchsia::accessibility::semantics::Role::CELL,
  513. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  514. ui::AXTreeID::CreateNewAXTreeID(), false,
  515. 0.0f, &mapper)
  516. .role());
  517. node.role = ax::mojom::Role::kColumnHeader;
  518. EXPECT_EQ(fuchsia::accessibility::semantics::Role::COLUMN_HEADER,
  519. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  520. ui::AXTreeID::CreateNewAXTreeID(), false,
  521. 0.0f, &mapper)
  522. .role());
  523. node.role = ax::mojom::Role::kRowGroup;
  524. EXPECT_EQ(fuchsia::accessibility::semantics::Role::ROW_GROUP,
  525. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  526. ui::AXTreeID::CreateNewAXTreeID(), false,
  527. 0.0f, &mapper)
  528. .role());
  529. node.role = ax::mojom::Role::kParagraph;
  530. EXPECT_EQ(fuchsia::accessibility::semantics::Role::PARAGRAPH,
  531. AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  532. ui::AXTreeID::CreateNewAXTreeID(), false,
  533. 0.0f, &mapper)
  534. .role());
  535. }
  536. TEST_F(AXTreeConverterTest, TransformUsesDeviceScalingWhenItIsNotZero) {
  537. ui::AXNodeData node_data;
  538. node_data.id = 1;
  539. ui::AXRelativeBounds relative_bounds = ui::AXRelativeBounds();
  540. relative_bounds.bounds = gfx::RectF(kRectX, kRectY, kRectWidth, kRectHeight);
  541. relative_bounds.transform =
  542. std::make_unique<gfx::Transform>(gfx::Transform::kSkipInitialization);
  543. relative_bounds.transform->MakeIdentity();
  544. relative_bounds.offset_container_id = -1;
  545. node_data.relative_bounds = relative_bounds;
  546. MockNodeIDMapper mapper;
  547. auto converted_node = AXNodeDataToSemanticNode(
  548. AddChildNode(node_data), root_node(), ui::AXTreeID::CreateNewAXTreeID(),
  549. false, 2.0f, &mapper);
  550. Node expected_node;
  551. expected_node.set_node_id(1);
  552. auto expected_transform = scenic::NewMatrix4Value(
  553. {0.5, 0, 0, 0, 0, 0.5, 0, 0, 0, 0, 1, 0, 0.5, 1, 0, 1});
  554. expected_node.set_transform(expected_transform.value);
  555. EXPECT_EQ(converted_node.transform().matrix,
  556. expected_node.transform().matrix);
  557. }
  558. TEST_F(AXTreeConverterTest, NodeWithTableAttributes) {
  559. CreateTableForTest();
  560. MockNodeIDMapper mapper;
  561. auto table_node = AXNodeDataToSemanticNode(root_node(), root_node(),
  562. ui::AXTreeID::CreateNewAXTreeID(),
  563. false, 0.0f, &mapper);
  564. EXPECT_EQ(table_node.role(), fuchsia::accessibility::semantics::Role::TABLE);
  565. EXPECT_EQ(table_node.attributes().table_attributes().number_of_columns(), 2u);
  566. EXPECT_EQ(table_node.attributes().table_attributes().number_of_rows(), 2u);
  567. auto* ax_row_node = root_node().tree()->GetFromId(kRowNodeId2);
  568. EXPECT_TRUE(ax_row_node);
  569. auto row_node = AXNodeDataToSemanticNode(*ax_row_node, root_node(),
  570. ui::AXTreeID::CreateNewAXTreeID(),
  571. false, 0.0f, &mapper);
  572. EXPECT_EQ(row_node.role(),
  573. fuchsia::accessibility::semantics::Role::TABLE_ROW);
  574. EXPECT_EQ(row_node.attributes().table_row_attributes().row_index(), 1u);
  575. auto* ax_cell_node = root_node().tree()->GetFromId(kCellNodeId);
  576. EXPECT_TRUE(ax_cell_node);
  577. auto cell_node = AXNodeDataToSemanticNode(*ax_cell_node, root_node(),
  578. ui::AXTreeID::CreateNewAXTreeID(),
  579. false, 0.0f, &mapper);
  580. EXPECT_EQ(cell_node.role(), fuchsia::accessibility::semantics::Role::CELL);
  581. EXPECT_EQ(cell_node.attributes().table_cell_attributes().row_index(), 1u);
  582. EXPECT_EQ(cell_node.attributes().table_cell_attributes().column_index(), 1u);
  583. }
  584. TEST_F(AXTreeConverterTest, IgnoredAndInvisibleNodesAreMarkedAsHidden) {
  585. MockNodeIDMapper mapper;
  586. ui::AXNodeData node;
  587. node.id = 0;
  588. node.AddState(ax::mojom::State::kInvisible);
  589. EXPECT_TRUE(AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  590. ui::AXTreeID::CreateNewAXTreeID(), false,
  591. 0.0f, &mapper)
  592. .states()
  593. .hidden());
  594. node.RemoveState(ax::mojom::State::kInvisible);
  595. node.AddState(ax::mojom::State::kIgnored);
  596. EXPECT_TRUE(AXNodeDataToSemanticNode(AddChildNode(node), root_node(),
  597. ui::AXTreeID::CreateNewAXTreeID(), false,
  598. 0.0f, &mapper)
  599. .states()
  600. .hidden());
  601. }
  602. } // namespace