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 }