1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial |
3 | |
4 | #include <chrono> |
5 | #define CATCH_CONFIG_MAIN |
6 | #include "catch2/catch.hpp" |
7 | |
8 | #include <slint.h> |
9 | #include <slint_image.h> |
10 | |
11 | SCENARIO("SharedString API" ) |
12 | { |
13 | slint::SharedString str; |
14 | |
15 | REQUIRE(str.empty()); |
16 | REQUIRE(str == "" ); |
17 | REQUIRE(std::string_view(str.data()) == "" ); // this test null termination of data() |
18 | |
19 | SECTION("Construct from string_view" ) |
20 | { |
21 | std::string foo("Foo" ); |
22 | std::string_view foo_view(foo); |
23 | str = foo_view; |
24 | REQUIRE(str == "Foo" ); |
25 | REQUIRE(std::string_view(str.data()) == "Foo" ); |
26 | } |
27 | |
28 | SECTION("Construct from char*" ) |
29 | { |
30 | str = "Bar" ; |
31 | REQUIRE(str == "Bar" ); |
32 | } |
33 | |
34 | SECTION("concatenate" ) |
35 | { |
36 | str = "Hello" ; |
37 | str += " " ; |
38 | str += slint::SharedString("🦊" ) + slint::SharedString("!" ); |
39 | REQUIRE(str == "Hello 🦊!" ); |
40 | REQUIRE(std::string_view(str.data()) == "Hello 🦊!" ); |
41 | } |
42 | |
43 | SECTION("begin/end" ) |
44 | { |
45 | str = "Hello" ; |
46 | REQUIRE(str.begin() + std::string_view(str).size() == str.end()); |
47 | } |
48 | } |
49 | |
50 | TEST_CASE("Basic SharedVector API" , "[vector]" ) |
51 | { |
52 | slint::SharedVector<int> vec; |
53 | REQUIRE(vec.empty()); |
54 | |
55 | SECTION("Initializer list" ) |
56 | { |
57 | slint::SharedVector<int> vec({ 1, 4, 10 }); |
58 | REQUIRE(vec.size() == 3); |
59 | REQUIRE(vec[0] == 1); |
60 | REQUIRE(vec[1] == 4); |
61 | REQUIRE(vec[2] == 10); |
62 | } |
63 | } |
64 | |
65 | TEST_CASE("Property Tracker" ) |
66 | { |
67 | using namespace slint::private_api; |
68 | PropertyTracker tracker1; |
69 | PropertyTracker tracker2; |
70 | Property<int> prop(42); |
71 | |
72 | auto r = tracker1.evaluate([&]() { return tracker2.evaluate([&]() { return prop.get(); }); }); |
73 | REQUIRE(r == 42); |
74 | |
75 | prop.set(1); |
76 | REQUIRE(tracker2.is_dirty()); |
77 | REQUIRE(tracker1.is_dirty()); |
78 | |
79 | r = tracker1.evaluate( |
80 | [&]() { return tracker2.evaluate_as_dependency_root([&]() { return prop.get(); }); }); |
81 | REQUIRE(r == 1); |
82 | prop.set(100); |
83 | REQUIRE(tracker2.is_dirty()); |
84 | REQUIRE(!tracker1.is_dirty()); |
85 | } |
86 | |
87 | TEST_CASE("Model row changes" ) |
88 | { |
89 | using namespace slint::private_api; |
90 | |
91 | auto model = std::make_shared<slint::VectorModel<int>>(); |
92 | |
93 | PropertyTracker tracker; |
94 | |
95 | REQUIRE(tracker.evaluate([&]() { |
96 | model->track_row_count_changes(); |
97 | return model->row_count(); |
98 | }) == 0); |
99 | REQUIRE(!tracker.is_dirty()); |
100 | model->push_back(1); |
101 | model->push_back(2); |
102 | REQUIRE(tracker.is_dirty()); |
103 | REQUIRE(tracker.evaluate([&]() { |
104 | model->track_row_count_changes(); |
105 | return model->row_count(); |
106 | }) == 2); |
107 | REQUIRE(!tracker.is_dirty()); |
108 | model->erase(0); |
109 | REQUIRE(tracker.is_dirty()); |
110 | REQUIRE(tracker.evaluate([&]() { |
111 | model->track_row_count_changes(); |
112 | return model->row_count(); |
113 | }) == 1); |
114 | } |
115 | |
116 | TEST_CASE("Track model row data changes" ) |
117 | { |
118 | using namespace slint::private_api; |
119 | |
120 | auto model = std::make_shared<slint::VectorModel<int>>(std::vector<int> { 0, 1, 2, 3, 4 }); |
121 | |
122 | PropertyTracker tracker; |
123 | |
124 | REQUIRE(tracker.evaluate([&]() { |
125 | model->track_row_data_changes(1); |
126 | return model->row_data(1); |
127 | }) == 1); |
128 | REQUIRE(!tracker.is_dirty()); |
129 | |
130 | model->set_row_data(2, 42); |
131 | REQUIRE(!tracker.is_dirty()); |
132 | model->set_row_data(1, 100); |
133 | REQUIRE(tracker.is_dirty()); |
134 | |
135 | REQUIRE(tracker.evaluate([&]() { |
136 | model->track_row_data_changes(1); |
137 | return model->row_data(1); |
138 | }) == 100); |
139 | REQUIRE(!tracker.is_dirty()); |
140 | |
141 | // Any changes to rows (even if after tracked rows) for now also marks watched rows as dirty, to |
142 | // keep the logic simple. |
143 | model->push_back(200); |
144 | REQUIRE(tracker.is_dirty()); |
145 | |
146 | REQUIRE(tracker.evaluate([&]() { |
147 | model->track_row_data_changes(1); |
148 | return model->row_data(1); |
149 | }) == 100); |
150 | REQUIRE(!tracker.is_dirty()); |
151 | |
152 | model->insert(0, 255); |
153 | REQUIRE(tracker.is_dirty()); |
154 | } |
155 | |
156 | TEST_CASE("Image" ) |
157 | { |
158 | using namespace slint; |
159 | |
160 | Image img; |
161 | { |
162 | auto size = img.size(); |
163 | REQUIRE(size.width == 0.); |
164 | REQUIRE(size.height == 0.); |
165 | } |
166 | { |
167 | REQUIRE(!img.path().has_value()); |
168 | } |
169 | |
170 | #ifndef SLINT_FEATURE_FREESTANDING |
171 | img = Image::load_from_path(SOURCE_DIR "/../../../logo/slint-logo-square-light-128x128.png" ); |
172 | { |
173 | auto size = img.size(); |
174 | REQUIRE(size.width == 128.); |
175 | REQUIRE(size.height == 128.); |
176 | } |
177 | { |
178 | auto actual_path = img.path(); |
179 | REQUIRE(actual_path.has_value()); |
180 | REQUIRE(*actual_path == SOURCE_DIR "/../../../logo/slint-logo-square-light-128x128.png" ); |
181 | } |
182 | #endif |
183 | |
184 | img = Image(SharedPixelBuffer<Rgba8Pixel> {}); |
185 | { |
186 | auto size = img.size(); |
187 | REQUIRE(size.width == 0); |
188 | REQUIRE(size.height == 0); |
189 | REQUIRE(!img.path().has_value()); |
190 | } |
191 | auto red = Rgb8Pixel { 0xff, 0, 0 }; |
192 | auto blu = Rgb8Pixel { 0, 0, 0xff }; |
193 | Rgb8Pixel some_data[] = { red, red, blu, red, blu, blu }; |
194 | img = Image(SharedPixelBuffer<Rgb8Pixel>(3, 2, some_data)); |
195 | { |
196 | auto size = img.size(); |
197 | REQUIRE(size.width == 3); |
198 | REQUIRE(size.height == 2); |
199 | REQUIRE(!img.path().has_value()); |
200 | } |
201 | } |
202 | |
203 | TEST_CASE("SharedVector" ) |
204 | { |
205 | using namespace slint; |
206 | |
207 | SharedVector<SharedString> vec; |
208 | vec.clear(); |
209 | vec.push_back("Hello" ); |
210 | vec.push_back("World" ); |
211 | vec.push_back("of" ); |
212 | vec.push_back("Vectors" ); |
213 | |
214 | auto copy = vec; |
215 | |
216 | REQUIRE(vec.size() == 4); |
217 | auto orig_cap = vec.capacity(); |
218 | REQUIRE(orig_cap >= vec.size()); |
219 | |
220 | vec.clear(); |
221 | REQUIRE(vec.size() == 0); |
222 | REQUIRE(vec.capacity() == 0); // vec was shared, so start with new empty vector. |
223 | vec.push_back("Welcome back" ); |
224 | REQUIRE(vec.size() == 1); |
225 | REQUIRE(vec.capacity() >= vec.size()); |
226 | |
227 | REQUIRE(copy.size() == 4); |
228 | REQUIRE(copy.capacity() == orig_cap); |
229 | |
230 | SharedVector<SharedString> vec2 { "Hello" , "World" , "of" , "Vectors" }; |
231 | REQUIRE(copy == vec2); |
232 | REQUIRE(copy != vec); |
233 | |
234 | copy.clear(); // copy is not shared (anymore), retain capacity. |
235 | REQUIRE(copy.capacity() == orig_cap); |
236 | |
237 | SharedVector<SharedString> vec3(2, "Welcome back" ); |
238 | REQUIRE(vec3.size() == 2); |
239 | REQUIRE(vec3[1] == "Welcome back" ); |
240 | REQUIRE(vec3 != vec); |
241 | |
242 | vec.push_back("Welcome back" ); |
243 | REQUIRE(vec3 == vec); |
244 | |
245 | SharedVector<int> vec4(5); |
246 | REQUIRE(vec4.size() == 5); |
247 | REQUIRE(vec4[3] == 0); |
248 | |
249 | std::vector<SharedString> std_v(vec2.begin(), vec2.end()); |
250 | SharedVector<SharedString> vec6(std_v.begin(), std_v.end()); |
251 | REQUIRE(vec6 == vec2); |
252 | } |
253 | |