• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }