• 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(UiModelTest,testRectBase)24 TEST(UiModelTest, 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 }
35 
TEST(UiModelTest,testRectOverlappingDimensions)36 TEST(UiModelTest, testRectOverlappingDimensions)
37 {
38     Rect rect0(100, 200, 300, 400);
39     Rect rect1(0, 100, 0, 100);
40     Rect rect2(200, 300, 400, 500);
41     Rect rect3(100, 150, 200, 350);
42     Rect rect4(150, 250, 350, 450);
43     Rect rect5(120, 180, 320, 380);
44 
45     int32_t dx = 0, dy = 0;
46     rect0.ComputeOverlappingDimensions(rect1, dx, dy); // no overlap
47     ASSERT_EQ(0, dx);
48     ASSERT_EQ(0, dy);
49     rect0.ComputeOverlappingDimensions(rect2, dx, dy); // no overlap
50     ASSERT_EQ(0, dx);
51     ASSERT_EQ(0, dy);
52     rect0.ComputeOverlappingDimensions(rect3, dx, dy); // x,y-overlap
53     ASSERT_EQ(50, dx);
54     ASSERT_EQ(50, dy);
55     rect0.ComputeOverlappingDimensions(rect4, dx, dy); // x,y-overlap
56     ASSERT_EQ(50, dx);
57     ASSERT_EQ(50, dy);
58     rect0.ComputeOverlappingDimensions(rect5, dx, dy); // fully contained
59     ASSERT_EQ(60, dx);
60     ASSERT_EQ(60, dy);
61 }
62 
TEST(UiModelTest,testRectIntersection)63 TEST(UiModelTest, testRectIntersection)
64 {
65     Rect rect0(100, 200, 300, 400);
66     Rect rect1(0, 100, 0, 100);
67     Rect rect2(200, 300, 400, 500);
68     Rect rect3(100, 150, 200, 350);
69     Rect rect4(150, 250, 350, 450);
70     Rect rect5(120, 180, 320, 380);
71 
72     Rect intersection {0, 0, 0, 0};
73     ASSERT_FALSE(rect0.ComputeIntersection(rect1, intersection)); // no overlap
74     ASSERT_FALSE(rect0.ComputeIntersection(rect2, intersection)); // no overlap
75     ASSERT_TRUE(rect0.ComputeIntersection(rect3, intersection)); // x,y-overlap
76     ASSERT_EQ(100, intersection.left_);
77     ASSERT_EQ(150, intersection.right_);
78     ASSERT_EQ(300, intersection.top_);
79     ASSERT_EQ(350, intersection.bottom_);
80     intersection = {0, 0, 0, 0};
81     ASSERT_TRUE(rect0.ComputeIntersection(rect4, intersection)); // x,y-overlap
82     ASSERT_EQ(150, intersection.left_);
83     ASSERT_EQ(200, intersection.right_);
84     ASSERT_EQ(350, intersection.top_);
85     ASSERT_EQ(400, intersection.bottom_);
86     intersection = {0, 0, 0, 0};
87     ASSERT_TRUE(rect0.ComputeIntersection(rect5, intersection)); // fully contained
88     ASSERT_EQ(120, intersection.left_);
89     ASSERT_EQ(180, intersection.right_);
90     ASSERT_EQ(320, intersection.top_);
91     ASSERT_EQ(380, intersection.bottom_);
92 }
93 
TEST(UiModelTest,testWidgetAttributes)94 TEST(UiModelTest, testWidgetAttributes)
95 {
96     Widget widget("hierarchy");
97     // get not-exist attribute, should return default value
98     ASSERT_EQ("none", widget.GetAttr(ATTR_TEXT, "none"));
99     // get exist attribute, should return actual value
100     widget.SetAttr(ATTR_TEXT, "wyz");
101     ASSERT_EQ("wyz", widget.GetAttr(ATTR_TEXT, "none"));
102 }
103 
104 /** NOTE:: Widget need to be movable since we need to move a constructed widget to
105  * its hosting tree. We must ensure that the move-created widget be same as the
106  * moved one (No any attribute/filed should be lost during moving).
107  * */
TEST(UiModelTest,testWidgetSafeMovable)108 TEST(UiModelTest, testWidgetSafeMovable)
109 {
110     Widget widget("hierarchy");
111     widget.SetAttr(ATTR_TEXT, "wyz");
112     widget.SetAttr(ATTR_ID, "100");
113     widget.SetHostTreeId("tree-10086");
114     widget.SetBounds(1, 2, 3, 4);
115 
116     auto newWidget = move(widget);
117     ASSERT_EQ("hierarchy", newWidget.GetHierarchy());
118     ASSERT_EQ("tree-10086", newWidget.GetHostTreeId());
119     ASSERT_EQ("wyz", newWidget.GetAttr(ATTR_TEXT, ""));
120     ASSERT_EQ("100", newWidget.GetAttr(ATTR_ID, ""));
121     auto bounds = newWidget.GetBounds();
122     ASSERT_EQ(1, bounds.left_);
123     ASSERT_EQ(2, bounds.right_);
124     ASSERT_EQ(3, bounds.top_);
125     ASSERT_EQ(4, bounds.bottom_);
126 }
127 
TEST(UiModelTest,testWidgetToStr)128 TEST(UiModelTest, testWidgetToStr)
129 {
130     Widget widget("hierarchy");
131 
132     // get exist attribute, should return default value
133     widget.SetAttr(ATTR_TEXT, "wyz");
134     widget.SetAttr(ATTR_ID, "100");
135 
136     auto str0 = widget.ToStr();
137     ASSERT_TRUE(str0.find(ATTR_TEXT) != string::npos);
138     ASSERT_TRUE(str0.find("wyz") != string::npos);
139     ASSERT_TRUE(str0.find(ATTR_ID) != string::npos);
140     ASSERT_TRUE(str0.find("100") != string::npos);
141 
142     // change attribute
143     widget.SetAttr(ATTR_ID, "211");
144     str0 = widget.ToStr();
145     ASSERT_TRUE(str0.find(ATTR_TEXT) != string::npos);
146     ASSERT_TRUE(str0.find("wyz") != string::npos);
147     ASSERT_TRUE(str0.find(ATTR_ID) != string::npos);
148     ASSERT_TRUE(str0.find("211") != string::npos);
149     ASSERT_TRUE(str0.find("100") == string::npos);
150 }
151 
152 // define a widget visitor to visit the specified attribute
153 class WidgetAttrVisitor : public WidgetVisitor {
154 public:
WidgetAttrVisitor(string_view attr)155     explicit WidgetAttrVisitor(string_view attr) : attr_(attr) {}
156 
~WidgetAttrVisitor()157     ~WidgetAttrVisitor() {}
158 
Visit(const Widget & widget)159     void Visit(const Widget &widget) override
160     {
161         if (!firstWidget_) {
162             attrValueSequence_ << ",";
163         } else {
164             firstWidget_ = false;
165         }
166         attrValueSequence_ << widget.GetAttr(attr_, "");
167     }
168 
169     stringstream attrValueSequence_;
170 
171 private:
172     const string attr_;
173     volatile bool firstWidget_ = true;
174 };
175 
176     static constexpr string_view DOM_TEXT = R"({
177 "attributes": {"resource-id": "id0"},
178 "children": [
179 {
180 "attributes": {"resource-id": "id00"},
181 "children": [
182 {
183 "attributes": {"resource-id": "id000"},
184 "children": [
185 {
186 "attributes": {"resource-id": "id0000"},
187 "children": []
188 }]}]},
189 {
190 "attributes": {"resource-id": "id01"},
191 "children": [
192 {
193 "attributes": {"resource-id": "id010"},
194 "children": []
195 }]}]})";
196 
TEST(UiModelTest,testConstructWidgetsFromDomCheckOrder)197 TEST(UiModelTest, testConstructWidgetsFromDomCheckOrder)
198 {
199     auto dom = nlohmann::json::parse(DOM_TEXT);
200     WidgetTree tree("tree");
201     tree.ConstructFromDom(dom, false);
202 
203     // visited the widget tree and check the 'resource-id' attribute
204     WidgetAttrVisitor visitor("resource-id");
205     tree.DfsTraverse(visitor);
206     // should collect correct attribute value sequence (DFS)
207     ASSERT_EQ("id0,id00,id000,id0000,id01,id010", visitor.attrValueSequence_.str()) << "Incorrect node order";
208 }
209 
210 class BoundsVisitor : public WidgetVisitor {
211 public:
Visit(const Widget & widget)212     void Visit(const Widget &widget) override
213     {
214         boundsList_.emplace_back(widget.GetBounds());
215     }
216 
217     vector<Rect> boundsList_;
218 };
219 
TEST(UiModelTest,testConstructWidgetsFromDomCheckBounds)220 TEST(UiModelTest, testConstructWidgetsFromDomCheckBounds)
221 {
222     auto dom = nlohmann::json::parse(R"({"attributes":{"bounds":"[0,-50][100,200]"},"children":[]})");
223     WidgetTree tree("tree");
224     tree.ConstructFromDom(dom, false);
225     BoundsVisitor visitor;
226     tree.DfsTraverse(visitor);
227     auto& bounds = visitor.boundsList_.at(0);
228     ASSERT_EQ(0, bounds.left_);
229     ASSERT_EQ(100, bounds.right_); // check converting negative number
230     ASSERT_EQ(-50, bounds.top_);
231     ASSERT_EQ(200, bounds.bottom_);
232 }
233 
TEST(UiModelTest,testGetRelativeNode)234 TEST(UiModelTest, testGetRelativeNode)
235 {
236     auto dom = nlohmann::json::parse(DOM_TEXT);
237     WidgetTree tree("tree");
238     tree.ConstructFromDom(dom, false);
239     BoundsVisitor visitor;
240     tree.DfsTraverse(visitor);
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     ASSERT_EQ("id0", rootPtr->GetAttr("resource-id", "")) << "Incorrect root node attribute";
246 
247     auto child1Ptr = tree.GetChildWidget(*rootPtr, 1);
248     ASSERT_TRUE(child1Ptr != nullptr) << "Failed to get child widget of root node at index 1";
249     ASSERT_EQ("id01", child1Ptr->GetAttr("resource-id", "")) << "Incorrect child node attribute";
250 
251     ASSERT_TRUE(tree.GetChildWidget(*rootPtr, 2) == nullptr) << "Unexpected child not";
252 }
253 
TEST(UiModelTest,testVisitNodesInGivenRoot)254 TEST(UiModelTest, testVisitNodesInGivenRoot)
255 {
256     auto dom = nlohmann::json::parse(DOM_TEXT);
257     WidgetTree tree("tree");
258     tree.ConstructFromDom(dom, false);
259 
260     auto rootPtr = tree.GetRootWidget();
261     ASSERT_TRUE(rootPtr != nullptr) << "Failed to get root node";
262     ASSERT_EQ(nullptr, tree.GetParentWidget(*rootPtr)) << "Root node should have no parent";
263 
264     auto child0Ptr = tree.GetChildWidget(*rootPtr, 0);
265     ASSERT_TRUE(child0Ptr != nullptr) << "Failed to get child widget of root node at index 0";
266 
267     WidgetAttrVisitor attrVisitor("resource-id");
268     tree.DfsTraverseDescendants(attrVisitor, *child0Ptr);
269     auto visitedTextSequence = attrVisitor.attrValueSequence_.str();
270     ASSERT_EQ("id00,id000,id0000", visitedTextSequence) << "Incorrect text sequence of node descendants";
271 }
272 
TEST(UiModelTest,testVisitFrontNodes)273 TEST(UiModelTest, testVisitFrontNodes)
274 {
275     auto dom = nlohmann::json::parse(DOM_TEXT);
276     WidgetTree tree("tree");
277     tree.ConstructFromDom(dom, false);
278 
279     auto rootPtr = tree.GetRootWidget();
280     ASSERT_TRUE(rootPtr != nullptr) << "Failed to get root node";
281     ASSERT_EQ(nullptr, tree.GetParentWidget(*rootPtr)) << "Root node should have no parent";
282 
283     auto child1Ptr = tree.GetChildWidget(*rootPtr, 1);
284     ASSERT_TRUE(child1Ptr != nullptr) << "Failed to get child widget of root node at index 0";
285 
286     WidgetAttrVisitor attrVisitor("resource-id");
287     tree.DfsTraverseFronts(attrVisitor, *child1Ptr);
288     auto visitedTextSequence = attrVisitor.attrValueSequence_.str();
289     ASSERT_EQ("id0,id00,id000,id0000", visitedTextSequence) << "Incorrect text sequence of front nodes";
290 }
291 
TEST(UiModelTest,testVisitTearNodes)292 TEST(UiModelTest, testVisitTearNodes)
293 {
294     auto dom = nlohmann::json::parse(DOM_TEXT);
295     WidgetTree tree("tree");
296     tree.ConstructFromDom(dom, false);
297 
298     auto rootPtr = tree.GetRootWidget();
299     ASSERT_TRUE(rootPtr != nullptr) << "Failed to get root node";
300     ASSERT_EQ(nullptr, tree.GetParentWidget(*rootPtr)) << "Root node should have no parent";
301 
302     auto child0Ptr = tree.GetChildWidget(*rootPtr, 0);
303     ASSERT_TRUE(child0Ptr != nullptr) << "Failed to get child widget of root node at index 0";
304 
305     WidgetAttrVisitor attrVisitor("resource-id");
306     tree.DfsTraverseRears(attrVisitor, *child0Ptr);
307     auto visitedTextSequence = attrVisitor.attrValueSequence_.str();
308     ASSERT_EQ("id000,id0000,id01,id010", visitedTextSequence) << "Incorrect text sequence of tear nodes";
309 }
310 
TEST(UiModelTest,testBoundsAndVisibilityCorrection)311 TEST(UiModelTest, testBoundsAndVisibilityCorrection)
312 {
313     constexpr string_view domText = R"(
314 {"attributes": {"resource-id": "id0","bounds": "[0,0][100,100]"},
315 "children": [
316 {"attributes": {"resource-id": "id00","bounds": "[0,20][100,80]"},
317 "children": [ {"attributes": {"resource-id": "id000","bounds": "[0,10][100,90]"}, "children": []} ]
318 },
319 {"attributes": {"resource-id": "id01","bounds": "[0,-20][100,100]"},
320 "children": [ {"attributes": {"resource-id": "id010","bounds": "[0,-20][100,0]"}, "children": []},
321 {"attributes": {"resource-id": "id011","bounds": "[0,-20][100,20]"}, "children": []}]
322 }
323 ]
324 })";
325 //                  id01 id010 id011
326 //                   |    |     |
327 //                   |    |     |
328 //  id0              |    |     |
329 //   |        id000  |          |
330 //   |   id00  |     |          |
331 //   |    |    |     |
332 //   |    |    |     |
333 //   |    |    |     |
334 //   |         |     |
335 //   |               |
336     auto dom = nlohmann::json::parse(domText);
337     WidgetTree tree("tree");
338     tree.ConstructFromDom(dom, true); // enable bounds amending
339     WidgetAttrVisitor attrVisitor("resource-id");
340     tree.DfsTraverse(attrVisitor);
341     // id010 should be discarded dut to totaly invisible
342     ASSERT_EQ("id0,id00,id000,id01,id011", attrVisitor.attrValueSequence_.str());
343     BoundsVisitor boundsVisitor;
344     tree.DfsTraverse(boundsVisitor);
345     // check revised bounds
346     vector<Rect> expectedBounds = { Rect {0, 100, 0, 100}, Rect {0, 100, 20, 80},
347         Rect {0, 100, 20, 80}, Rect {0, 100, 0, 100}, Rect {0, 100, 0, 20} };
348     ASSERT_EQ(expectedBounds.size(), boundsVisitor.boundsList_.size());
349     for (auto index = 0; index < expectedBounds.size(); index++) {
350         auto& expectedBound = expectedBounds.at(index);
351         auto& actualBound = boundsVisitor.boundsList_.at(index);
352         ASSERT_EQ(expectedBound.left_, actualBound.left_);
353         ASSERT_EQ(expectedBound.right_, actualBound.right_);
354         ASSERT_EQ(expectedBound.top_, actualBound.top_);
355         ASSERT_EQ(expectedBound.bottom_, actualBound.bottom_);
356     }
357 }