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

source code of slint/api/cpp/tests/datastructures.cpp