1 /*
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "gtest/gtest.h"
16 #include "ui_model.h"
17
18 using namespace OHOS::uitest;
19 using namespace std;
20
21 static constexpr auto ATTR_TEXT = "text";
22 static constexpr auto ATTR_ID = "id";
23
TEST(RectTest,testRectBase)24 TEST(RectTest, testRectBase)
25 {
26 Rect rect(100, 200, 300, 400);
27 ASSERT_EQ(100, rect.left_);
28 ASSERT_EQ(200, rect.right_);
29 ASSERT_EQ(300, rect.top_);
30 ASSERT_EQ(400, rect.bottom_);
31
32 ASSERT_EQ(rect.GetCenterX(), (100 + 200) / 2);
33 ASSERT_EQ(rect.GetCenterY(), (300 + 400) / 2);
34 ASSERT_EQ(rect.GetHeight(), 400 - 300);
35 ASSERT_EQ(rect.GetWidth(), 200 - 100);
36 }
37
TEST(WidgetTest,testAttributes)38 TEST(WidgetTest, testAttributes)
39 {
40 Widget widget("hierarchy");
41 // get not-exist attribute, should return default value
42 ASSERT_EQ("none", widget.GetAttr(ATTR_TEXT, "none"));
43 // get exist attribute, should return actual value
44 widget.SetAttr(ATTR_TEXT, "wyz");
45 ASSERT_EQ("wyz", widget.GetAttr(ATTR_TEXT, "none"));
46 }
47
48 /** NOTE:: Widget need to be movable since we need to move a constructed widget to
49 * its hosting tree. We must ensure that the move-created widget be same as the
50 * moved one (No any attribute/filed should be lost during moving).
51 * */
TEST(WidgetTest,testSafeMovable)52 TEST(WidgetTest, testSafeMovable)
53 {
54 Widget widget("hierarchy");
55 widget.SetAttr(ATTR_TEXT, "wyz");
56 widget.SetAttr(ATTR_ID, "100");
57 widget.SetHostTreeId("tree-10086");
58 widget.SetBounds(Rect(1, 2, 3, 4));
59
60 auto newWidget = move(widget);
61 ASSERT_EQ("hierarchy", newWidget.GetHierarchy());
62 ASSERT_EQ("tree-10086", newWidget.GetHostTreeId());
63 ASSERT_EQ("wyz", newWidget.GetAttr(ATTR_TEXT, ""));
64 ASSERT_EQ("100", newWidget.GetAttr(ATTR_ID, ""));
65 auto bounds = newWidget.GetBounds();
66 ASSERT_EQ(1, bounds.left_);
67 ASSERT_EQ(2, bounds.right_);
68 ASSERT_EQ(3, bounds.top_);
69 ASSERT_EQ(4, bounds.bottom_);
70 }
71
TEST(WidgetTest,testToStr)72 TEST(WidgetTest, testToStr)
73 {
74 Widget widget("hierarchy");
75
76 // get exist attribute, should return default value
77 widget.SetAttr(ATTR_TEXT, "wyz");
78 widget.SetAttr(ATTR_ID, "100");
79
80 auto str0 = widget.ToStr();
81 ASSERT_TRUE(str0.find(ATTR_TEXT) != string::npos);
82 ASSERT_TRUE(str0.find("wyz") != string::npos);
83 ASSERT_TRUE(str0.find(ATTR_ID) != string::npos);
84 ASSERT_TRUE(str0.find("100") != string::npos);
85
86 // change attribute
87 widget.SetAttr(ATTR_ID, "211");
88 str0 = widget.ToStr();
89 ASSERT_TRUE(str0.find(ATTR_TEXT) != string::npos);
90 ASSERT_TRUE(str0.find("wyz") != string::npos);
91 ASSERT_TRUE(str0.find(ATTR_ID) != string::npos);
92 ASSERT_TRUE(str0.find("211") != string::npos);
93 ASSERT_TRUE(str0.find("100") == string::npos);
94 }
95
96 // define a widget visitor to visit the specified attribute
97 class WidgetAttrVisitor : public WidgetVisitor {
98 public:
WidgetAttrVisitor(string_view attr)99 explicit WidgetAttrVisitor(string_view attr) : attr_(attr) {}
100
~WidgetAttrVisitor()101 ~WidgetAttrVisitor() {}
102
Visit(const Widget & widget)103 void Visit(const Widget &widget) override
104 {
105 if (!firstWidget_) {
106 attrValueSequence_ << ",";
107 } else {
108 firstWidget_ = false;
109 }
110 attrValueSequence_ << widget.GetAttr(attr_, "");
111 }
112
113 stringstream attrValueSequence_;
114
115 private:
116 const string attr_;
117 volatile bool firstWidget_ = true;
118 };
119
120 static constexpr string_view DOM_TEXT = R"({
121 "attributes": {"resource-id": "id0"},
122 "children": [
123 {
124 "attributes": {"resource-id": "id00"},
125 "children": [
126 {
127 "attributes": {"resource-id": "id000"},
128 "children": [
129 {
130 "attributes": {"resource-id": "id0000"},
131 "children": []
132 }]}]},
133 {
134 "attributes": {"resource-id": "id01"},
135 "children": [
136 {
137 "attributes": {"resource-id": "id010"},
138 "children": []
139 }]}]})";
140
TEST(WidgetTreeTest,testConstructWidgetsFromDomCheckOrder)141 TEST(WidgetTreeTest, testConstructWidgetsFromDomCheckOrder)
142 {
143 auto dom = nlohmann::json::parse(DOM_TEXT);
144 WidgetTree tree("tree");
145 tree.ConstructFromDom(dom, false);
146
147 // visited the widget tree and check the 'resource-id' attribute
148 WidgetAttrVisitor visitor("resource-id");
149 tree.DfsTraverse(visitor);
150 // should collect correct attribute value sequence (DFS)
151 ASSERT_EQ("id0,id00,id000,id0000,id01,id010", visitor.attrValueSequence_.str()) << "Incorrect node order";
152 }
153
154 class BoundsVisitor : public WidgetVisitor {
155 public:
Visit(const Widget & widget)156 void Visit(const Widget &widget) override
157 {
158 boundsList_.emplace_back(widget.GetBounds());
159 }
160
161 vector<Rect> boundsList_;
162 };
163
TEST(WidgetTreeTest,testConstructWidgetsFromDomCheckBounds)164 TEST(WidgetTreeTest, testConstructWidgetsFromDomCheckBounds)
165 {
166 auto dom = nlohmann::json::parse(R"({"attributes":{"bounds":"[0,-50][100,200]"}, "children":[]})");
167 WidgetTree tree("tree");
168 tree.ConstructFromDom(dom, false);
169 BoundsVisitor visitor;
170 tree.DfsTraverse(visitor);
171 auto &bounds = visitor.boundsList_.at(0);
172 ASSERT_EQ(0, bounds.left_);
173 ASSERT_EQ(100, bounds.right_); // check converting negative number
174 ASSERT_EQ(-50, bounds.top_);
175 ASSERT_EQ(200, bounds.bottom_);
176 }
177
TEST(WidgetTreeTest,testGetRelativeNode)178 TEST(WidgetTreeTest, testGetRelativeNode)
179 {
180 auto dom = nlohmann::json::parse(DOM_TEXT);
181 WidgetTree tree("tree");
182 tree.ConstructFromDom(dom, false);
183 BoundsVisitor visitor;
184 tree.DfsTraverse(visitor);
185
186 auto rootPtr = tree.GetRootWidget();
187 ASSERT_TRUE(rootPtr != nullptr) << "Failed to get root node";
188 ASSERT_EQ(nullptr, tree.GetParentWidget(*rootPtr)) << "Root node should have no parent";
189 ASSERT_EQ("id0", rootPtr->GetAttr("resource-id", "")) << "Incorrect root node attribute";
190
191 auto child1Ptr = tree.GetChildWidget(*rootPtr, 1);
192 ASSERT_TRUE(child1Ptr != nullptr) << "Failed to get child widget of root node at index 1";
193 ASSERT_EQ("id01", child1Ptr->GetAttr("resource-id", "")) << "Incorrect child node attribute";
194
195 ASSERT_TRUE(tree.GetChildWidget(*rootPtr, 2) == nullptr) << "Unexpected child not";
196 }
197
TEST(WidgetTreeTest,testVisitNodesInGivenRoot)198 TEST(WidgetTreeTest, testVisitNodesInGivenRoot)
199 {
200 auto dom = nlohmann::json::parse(DOM_TEXT);
201 WidgetTree tree("tree");
202 tree.ConstructFromDom(dom, false);
203
204 auto rootPtr = tree.GetRootWidget();
205 ASSERT_TRUE(rootPtr != nullptr) << "Failed to get root node";
206 ASSERT_EQ(nullptr, tree.GetParentWidget(*rootPtr)) << "Root node should have no parent";
207
208 auto child0Ptr = tree.GetChildWidget(*rootPtr, 0);
209 ASSERT_TRUE(child0Ptr != nullptr) << "Failed to get child widget of root node at index 0";
210
211 WidgetAttrVisitor attrVisitor("resource-id");
212 tree.DfsTraverseDescendants(attrVisitor, *child0Ptr);
213 auto visitedTextSequence = attrVisitor.attrValueSequence_.str();
214 ASSERT_EQ("id00,id000,id0000", visitedTextSequence) << "Incorrect text sequence of node descendants";
215 }
216
TEST(WidgetTreeTest,testVisitFrontNodes)217 TEST(WidgetTreeTest, testVisitFrontNodes)
218 {
219 auto dom = nlohmann::json::parse(DOM_TEXT);
220 WidgetTree tree("tree");
221 tree.ConstructFromDom(dom, false);
222
223 auto rootPtr = tree.GetRootWidget();
224 ASSERT_TRUE(rootPtr != nullptr) << "Failed to get root node";
225 ASSERT_EQ(nullptr, tree.GetParentWidget(*rootPtr)) << "Root node should have no parent";
226
227 auto child1Ptr = tree.GetChildWidget(*rootPtr, 1);
228 ASSERT_TRUE(child1Ptr != nullptr) << "Failed to get child widget of root node at index 0";
229
230 WidgetAttrVisitor attrVisitor("resource-id");
231 tree.DfsTraverseFronts(attrVisitor, *child1Ptr);
232 auto visitedTextSequence = attrVisitor.attrValueSequence_.str();
233 ASSERT_EQ("id0,id00,id000,id0000", visitedTextSequence) << "Incorrect text sequence of front nodes";
234 }
235
TEST(WidgetTreeTest,testVisitTearNodes)236 TEST(WidgetTreeTest, testVisitTearNodes)
237 {
238 auto dom = nlohmann::json::parse(DOM_TEXT);
239 WidgetTree tree("tree");
240 tree.ConstructFromDom(dom, false);
241
242 auto rootPtr = tree.GetRootWidget();
243 ASSERT_TRUE(rootPtr != nullptr) << "Failed to get root node";
244 ASSERT_EQ(nullptr, tree.GetParentWidget(*rootPtr)) << "Root node should have no parent";
245
246 auto child0Ptr = tree.GetChildWidget(*rootPtr, 0);
247 ASSERT_TRUE(child0Ptr != nullptr) << "Failed to get child widget of root node at index 0";
248
249 WidgetAttrVisitor attrVisitor("resource-id");
250 tree.DfsTraverseRears(attrVisitor, *child0Ptr);
251 auto visitedTextSequence = attrVisitor.attrValueSequence_.str();
252 ASSERT_EQ("id000,id0000,id01,id010", visitedTextSequence) << "Incorrect text sequence of tear nodes";
253 }
254
TEST(WidgetTreeTest,testBoundsAndVisibilityCorrection)255 TEST(WidgetTreeTest, testBoundsAndVisibilityCorrection)
256 {
257 constexpr string_view domText = R"(
258 {"attributes" : {"resource-id" : "id0","bounds" : "[0,0][100,100]"},
259 "children": [
260 {"attributes" : { "resource-id" : "id00","bounds" : "[0,20][100,80]" },
261 "children": [ {"attributes": {"resource-id": "id000","bounds": "[0,10][100,90]"}, "children": []} ]
262 },
263 {"attributes": {"resource-id": "id01","bounds": "[0,-20][100,100]"},
264 "children": [ {"attributes": {"resource-id": "id010","bounds": "[0,-20][100,0]"}, "children": []},
265 {"attributes": {"resource-id": "id011","bounds": "[0,-20][100,20]"}, "children": []}]
266 }
267 ]
268 })";
269 // id01 id010 id011
270 // | | |
271 // | | |
272 // id0 | | |
273 // | id000 | |
274 // | id00 | | |
275 // | | | |
276 // | | | |
277 // | | | |
278 // | | |
279 // | |
280 auto dom = nlohmann::json::parse(domText);
281 WidgetTree tree("tree");
282 tree.ConstructFromDom(dom, true); // enable bounds amending
283 WidgetAttrVisitor attrVisitor("resource-id");
284 tree.DfsTraverse(attrVisitor);
285 // id010 should be discarded dut to totaly invisible
286 ASSERT_EQ("id0,id00,id000,id01,id011", attrVisitor.attrValueSequence_.str());
287 BoundsVisitor boundsVisitor;
288 tree.DfsTraverse(boundsVisitor);
289 // check revised bounds
290 vector<Rect> expectedBounds = {Rect(0, 100, 0, 100), Rect(0, 100, 20, 80), Rect(0, 100, 20, 80),
291 Rect(0, 100, 0, 100), Rect(0, 100, 0, 20)};
292 ASSERT_EQ(expectedBounds.size(), boundsVisitor.boundsList_.size());
293 for (size_t index = 0; index < expectedBounds.size(); index++) {
294 auto &expectedBound = expectedBounds.at(index);
295 auto &actualBound = boundsVisitor.boundsList_.at(index);
296 ASSERT_EQ(expectedBound.left_, actualBound.left_);
297 ASSERT_EQ(expectedBound.right_, actualBound.right_);
298 ASSERT_EQ(expectedBound.top_, actualBound.top_);
299 ASSERT_EQ(expectedBound.bottom_, actualBound.bottom_);
300 }
301 }
302
TEST(WidgetTreeTest,testMarshalIntoDom)303 TEST(WidgetTreeTest, testMarshalIntoDom)
304 {
305 auto dom = nlohmann::json::parse(DOM_TEXT);
306 WidgetTree tree("tree");
307 tree.ConstructFromDom(dom, false);
308 auto dom1 = nlohmann::json();
309 tree.MarshalIntoDom(dom1);
310 ASSERT_FALSE(dom1.empty());
311 }
312
313 /** Make merged tree from doms and collects the target attribute dfs sequence.*/
CheckMergedTree(const array<string_view,3> & doms,map<string,string> & attrCollector)314 static void CheckMergedTree(const array<string_view, 3> &doms, map<string, string> &attrCollector)
315 {
316 WidgetTree tree("");
317 vector<unique_ptr<WidgetTree>> subTrees;
318 for (auto &dom : doms) {
319 auto tempTree = make_unique<WidgetTree>("");
320 tempTree->ConstructFromDom(nlohmann::json::parse(dom), true);
321 subTrees.push_back(move(tempTree));
322 }
323 WidgetTree::MergeTrees(subTrees, tree);
324 for (auto &[name, value] : attrCollector) {
325 if (name == "bounds") {
326 BoundsVisitor visitor;
327 tree.DfsTraverse(visitor);
328 stringstream stream;
329 for (auto &rect : visitor.boundsList_) {
330 stream << "[" << rect.left_ << "," << rect.top_ << "][" << rect.right_ << "," << rect.bottom_ << "]";
331 stream << ",";
332 }
333 attrCollector[name] = stream.str();
334 } else {
335 WidgetAttrVisitor visitor(name);
336 tree.DfsTraverse(visitor);
337 attrCollector[name] = visitor.attrValueSequence_.str();
338 }
339 }
340 }
341
TEST(WidgetTreeTest,testMergeTreesNoIntersection)342 TEST(WidgetTreeTest, testMergeTreesNoIntersection)
343 {
344 // 3 tree vertical/horizontal arranged without intersection
345 constexpr string_view domText0 = R"(
346 {
347 "attributes": {"id": "t0-id0", "bounds": "[0,0][2,2]"},
348 "children": [{"attributes": {"id": "t0-id00", "bounds": "[0,0][2,1]"}, "children": []}]
349 })";
350 constexpr string_view domText1 = R"(
351 {
352 "attributes": {"id": "t1-id0", "bounds": "[0,2][2,4]"},
353 "children": [{"attributes": {"id": "t1-id00", "bounds": "[0,2][2,3]"}, "children": []}]
354 })";
355 constexpr string_view domText2 = R"(
356 {
357 "attributes": {"id": "t2-id0", "bounds": "[2,0][4,4]"},
358 "children": [{"attributes": {"id": "t2-id00", "bounds": "[2,0][4,3]"}, "children": []}]
359 })";
360 map<string, string> attrs;
361 attrs["id"] = "";
362 attrs["bounds"] = "";
363 CheckMergedTree( {domText0, domText1, domText2}, attrs);
364 // all widgets should be available (leading ',': separator of virtual-root node attr-value)
365 ASSERT_EQ(",t0-id0,t0-id00,t1-id0,t1-id00,t2-id0,t2-id00", attrs["id"]);
366 // bounds should not be revised (leading '[0,0][4,4]': auto-computed virtual-root node bounds)
367 ASSERT_EQ("[0,0][4,4],[0,0][2,2],[0,0][2,1],[0,2][2,4],[0,2][2,3],[2,0][4,4],[2,0][4,3],", attrs["bounds"]);
368 }
369
TEST(WidgetTreeTest,testMergeTreesWithFullyCovered)370 TEST(WidgetTreeTest, testMergeTreesWithFullyCovered)
371 {
372 // dom2 is fully covered by dom0 and dom1
373 constexpr string_view domText0 = R"(
374 {
375 "attributes": {"id": "t0-id0", "bounds": "[0,0][2,2]"},
376 "children": [{"attributes": {"id": "t0-id00", "bounds": "[0,0][2,1]"}, "children": []}]
377 })";
378 constexpr string_view domText1 = R"(
379 {
380 "attributes": {"id": "t1-id0", "bounds": "[0,2][2,4]"},
381 "children": [{"attributes": {"id": "t1-id00", "bounds": "[0,2][2,3]"}, "children": []}]
382 })";
383 constexpr string_view domText2 = R"(
384 {
385 "attributes": {"id": "t2-id0", "bounds": "[0,0][2,4]"},
386 "children": [{"attributes": {"id": "t2-id00", "bounds": "[2,0][4,3]"}, "children": []}]
387 })";
388 map<string, string> attrs;
389 attrs["id"] = "";
390 attrs["bounds"] = "";
391 CheckMergedTree( {domText0, domText1, domText2}, attrs);
392 // tree2 widgets should be discarded (leading ',': separator of virtual-root node attr-value)
393 ASSERT_EQ(",t0-id0,t0-id00,t1-id0,t1-id00", attrs["id"]);
394 // bounds should not be revised (leading '[0,0][2,4]': auto-computed virtual-root node bounds)
395 ASSERT_EQ("[0,0][2,4],[0,0][2,2],[0,0][2,1],[0,2][2,4],[0,2][2,3],", attrs["bounds"]);
396 }
397
TEST(WidgetTreeTest,testMergeTreesWithPartialCovered)398 TEST(WidgetTreeTest, testMergeTreesWithPartialCovered)
399 {
400 constexpr string_view domText0 = R"(
401 {
402 "attributes": {"id": "t0-id0", "bounds": "[0,0][4,4]"},
403 "children": [{"attributes": {"id": "t0-id00", "bounds": "[0,0][4,2]"}, "children": []}]
404 })";
405 // t1-id0 is partial covered by tree0, t1-id00 is fully covered
406 constexpr string_view domText1 = R"(
407 {
408 "attributes": {"id": "t1-id0", "bounds": "[0,2][4,6]"},
409 "children": [{"attributes": {"id": "t1-id00", "bounds": "[0,2][4,4]"}, "children": []}]
410 })";
411 // t2-id0 is partial covered by tree0/tree1, t2-id00 is fully covered by tree0/tree1
412 constexpr string_view domText2 = R"(
413 {
414 "attributes": {"id": "t2-id0", "bounds": "[0,0][4,8]"},
415 "children": [{"attributes": {"id": "t2-id00", "bounds": "[0,0][4,5]"}, "children": []}]
416 })";
417 map<string, string> attrs;
418 attrs["id"] = "";
419 attrs["bounds"] = "";
420 CheckMergedTree( {domText0, domText1, domText2}, attrs);
421 // check visible widgets (leading ',': separator of virtual-root node attr-value)
422 ASSERT_EQ(",t0-id0,t0-id00,t1-id0,t2-id0", attrs["id"]);
423 // bounds should not be revised (leading '[0,0][2,4]': auto-computed virtual-root node bounds)
424 // t1-id0: [0,4][4,6]; t2-id0: [0,6][4,8]
425 ASSERT_EQ("[0,0][4,8],[0,0][4,4],[0,0][4,2],[0,4][4,6],[0,6][4,8],", attrs["bounds"]);
426 }