• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

paulmthompson / WhiskerToolbox / 18685379784

21 Oct 2025 01:25PM UTC coverage: 72.522% (+0.1%) from 72.391%
18685379784

push

github

paulmthompson
fix failing tests

18 of 40 new or added lines in 1 file covered. (45.0%)

1765 existing lines in 32 files now uncovered.

53998 of 74457 relevant lines covered (72.52%)

46177.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.48
/src/DataManager/Points/Point_Data.test.cpp
1
#include "Points/Point_Data.hpp"
2
#include "TimeFrame/interval_data.hpp"
3
#include "TimeFrame/TimeFrame.hpp"
4
#include "Entity/EntityRegistry.hpp"
5
#include "DataManager.hpp"
6
#include <catch2/catch_test_macros.hpp>
7
#include <catch2/catch_approx.hpp>
8

9
TEST_CASE("DM - PointData - Core functionality", "[points][data][core]") {
17✔
10
    PointData point_data;
17✔
11

12
    // Setup some test data
13
    Point2D<float> p1{1.0f, 2.0f};
17✔
14
    Point2D<float> p2{3.0f, 4.0f};
17✔
15
    Point2D<float> p3{5.0f, 6.0f};
17✔
16
    std::vector<Point2D<float>> points = {p1, p2};
51✔
17
    std::vector<Point2D<float>> more_points = {p3};
51✔
18

19
    SECTION("Adding and retrieving points at time") {
17✔
20
        // Add a single point at time 10
21
        point_data.addAtTime(TimeFrameIndex(10), p1);
1✔
22

23
        auto points_at_10 = point_data.getAtTime(TimeFrameIndex(10));
1✔
24
        REQUIRE(points_at_10.size() == 1);
1✔
25
        REQUIRE(points_at_10[0].x == Catch::Approx(1.0f));
1✔
26
        REQUIRE(points_at_10[0].y == Catch::Approx(2.0f));
1✔
27

28
        // Add another point at the same time
29
        point_data.addAtTime(TimeFrameIndex(10), p2);
1✔
30
        points_at_10 = point_data.getAtTime(TimeFrameIndex(10));
1✔
31
        REQUIRE(points_at_10.size() == 2);
1✔
32
        REQUIRE(points_at_10[1].x == Catch::Approx(3.0f));
1✔
33
        REQUIRE(points_at_10[1].y == Catch::Approx(4.0f));
1✔
34

35
        // Add points at a different time
36
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points);
1✔
37
        auto points_at_20 = point_data.getAtTime(TimeFrameIndex(20));
1✔
38
        REQUIRE(points_at_20.size() == 1);
1✔
39
        REQUIRE(points_at_20[0].x == Catch::Approx(5.0f));
1✔
40
        REQUIRE(points_at_20[0].y == Catch::Approx(6.0f));
1✔
41
    }
18✔
42

43
    SECTION("Overwriting points at time") {
17✔
44
        // Add initial points
45
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
46

47
        // Overwrite with a single point
48
        point_data.overwritePointAtTime(TimeFrameIndex(10), p3);
1✔
49
        auto points_at_10 = point_data.getAtTime(TimeFrameIndex(10));
1✔
50
        REQUIRE(points_at_10.size() == 1);
1✔
51
        REQUIRE(points_at_10[0].x == Catch::Approx(5.0f));
1✔
52
        REQUIRE(points_at_10[0].y == Catch::Approx(6.0f));
1✔
53

54
        // Overwrite with multiple points
55
        point_data.overwritePointsAtTime(TimeFrameIndex(10), points);
1✔
56
        points_at_10 = point_data.getAtTime(TimeFrameIndex(10));
1✔
57
        REQUIRE(points_at_10.size() == 2);
1✔
58
        REQUIRE(points_at_10[0].x == Catch::Approx(1.0f));
1✔
59
        REQUIRE(points_at_10[0].y == Catch::Approx(2.0f));
1✔
60
    }
18✔
61

62
    SECTION("Clearing points at time") {
17✔
63
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
64
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points);
1✔
65

66
        static_cast<void>(point_data.clearAtTime(TimeFrameIndex(10)));
1✔
67

68
        auto points_at_10 = point_data.getAtTime(TimeFrameIndex(10));
1✔
69
        auto points_at_20 = point_data.getAtTime(TimeFrameIndex(20));
1✔
70

71
        REQUIRE(points_at_10.empty());
1✔
72
        REQUIRE(points_at_20.size() == 1);
1✔
73
    }
18✔
74

75
    SECTION("GetAllPointsAsRange functionality") {
17✔
76
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
77
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points);
1✔
78

79
        size_t count = 0;
1✔
80
        int64_t first_time = 0;
1✔
81
        int64_t second_time = 0;
1✔
82

83
        for (const auto& pair : point_data.GetAllPointsAsRange()) {
3✔
84
            if (count == 0) {
2✔
85
                first_time = pair.time.getValue();
1✔
86
                REQUIRE(pair.points.size() == 2);
1✔
87
            } else if (count == 1) {
1✔
88
                second_time = pair.time.getValue();
1✔
89
                REQUIRE(pair.points.size() == 1);
1✔
90
            }
91
            count++;
2✔
92
        }
2✔
93

94
        REQUIRE(count == 2);
1✔
95
        REQUIRE(first_time == 10);
1✔
96
        REQUIRE(second_time == 20);
1✔
97
    }
17✔
98

99
    SECTION("GetPointsInRange functionality") {
17✔
100
        // Setup data at multiple time points
101
        point_data.addPointsAtTime(TimeFrameIndex(5), points);       // 2 points
9✔
102
        point_data.addPointsAtTime(TimeFrameIndex(10), points);      // 2 points  
9✔
103
        point_data.addPointsAtTime(TimeFrameIndex(15), more_points); // 1 point
9✔
104
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points); // 1 point
9✔
105
        point_data.addPointsAtTime(TimeFrameIndex(25), points);      // 2 points
9✔
106

107
        SECTION("Range includes some data") {
9✔
108
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(20)};
1✔
109
            size_t count = 0;
1✔
110
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
4✔
111
                if (count == 0) {
3✔
112
                    REQUIRE(pair.time.getValue() == 10);
1✔
113
                    REQUIRE(pair.points.size() == 2);
1✔
114
                } else if (count == 1) {
2✔
115
                    REQUIRE(pair.time.getValue() == 15);
1✔
116
                    REQUIRE(pair.points.size() == 1);
1✔
117
                } else if (count == 2) {
1✔
118
                    REQUIRE(pair.time.getValue() == 20);
1✔
119
                    REQUIRE(pair.points.size() == 1);
1✔
120
                }
121
                count++;
3✔
122
            }
3✔
123
            REQUIRE(count == 3); // Should include times 10, 15, 20
1✔
124
        }
9✔
125

126
        SECTION("Range includes all data") {
9✔
127
            TimeFrameInterval interval{TimeFrameIndex(0), TimeFrameIndex(30)};
1✔
128
            size_t count = 0;
1✔
129
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
6✔
130
                count++;
5✔
131
            }
5✔
132
            REQUIRE(count == 5); // Should include all 5 time points
1✔
133
        }
9✔
134

135
        SECTION("Range includes no data") {
9✔
136
            TimeFrameInterval interval{TimeFrameIndex(100), TimeFrameIndex(200)};
1✔
137
            size_t count = 0;
1✔
138
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
1✔
139
                count++;
×
140
            }
×
141
            REQUIRE(count == 0); // Should be empty
1✔
142
        }
9✔
143

144
        SECTION("Range with single time point") {
9✔
145
            TimeFrameInterval interval{TimeFrameIndex(15), TimeFrameIndex(15)};
1✔
146
            size_t count = 0;
1✔
147
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
2✔
148
                REQUIRE(pair.time.getValue() == 15);
1✔
149
                REQUIRE(pair.points.size() == 1);
1✔
150
                count++;
1✔
151
            }
1✔
152
            REQUIRE(count == 1); // Should include only time 15
1✔
153
        }
9✔
154

155
        SECTION("Range with start > end") {
9✔
156
            TimeFrameInterval interval{TimeFrameIndex(20), TimeFrameIndex(10)};
1✔
157
            size_t count = 0;
1✔
158
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
1✔
159
                count++;
×
160
            }
×
161
            REQUIRE(count == 0); // Should be empty when start > end
1✔
162
        }
9✔
163

164
        SECTION("Range with timeframe conversion - same timeframes") {
9✔
165
            // Test with same source and target timeframes
166
            std::vector<int> times = {5, 10, 15, 20, 25};
3✔
167
            auto timeframe = std::make_shared<TimeFrame>(times);
1✔
168
            
169
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(20)};
1✔
170
            size_t count = 0;
1✔
171
            for (const auto& pair : point_data.GetPointsInRange(interval, timeframe.get(), timeframe.get())) {
4✔
172
                if (count == 0) {
3✔
173
                    REQUIRE(pair.time.getValue() == 10);
1✔
174
                    REQUIRE(pair.points.size() == 2);
1✔
175
                } else if (count == 1) {
2✔
176
                    REQUIRE(pair.time.getValue() == 15);
1✔
177
                    REQUIRE(pair.points.size() == 1);
1✔
178
                } else if (count == 2) {
1✔
179
                    REQUIRE(pair.time.getValue() == 20);
1✔
180
                    REQUIRE(pair.points.size() == 1);
1✔
181
                }
182
                count++;
3✔
183
            }
3✔
184
            REQUIRE(count == 3); // Should include times 10, 15, 20
1✔
185
        }
10✔
186

187
        SECTION("Range with timeframe conversion - different timeframes") {
9✔
188
            // Create a separate point data instance for timeframe conversion test
189
            PointData timeframe_test_data;
1✔
190
            
191
            // Create source timeframe (video frames)
192
            std::vector<int> video_times = {0, 10, 20, 30, 40};  
3✔
193
            auto video_timeframe = std::make_shared<TimeFrame>(video_times);
1✔
194
            
195
            // Create target timeframe (data sampling)
196
            std::vector<int> data_times = {0, 5, 10, 15, 20, 25, 30, 35, 40}; 
3✔
197
            auto data_timeframe = std::make_shared<TimeFrame>(data_times);
1✔
198
            
199
            // Add data at target timeframe indices
200
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(2), points);  // At data timeframe index 2 (time=10)
1✔
201
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(3), more_points);  // At data timeframe index 3 (time=15)
1✔
202
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(4), points);  // At data timeframe index 4 (time=20)
1✔
203
            
204
            // Query video frames 1-2 (times 10-20) which should map to data indices 2-4 (times 10-20)
205
            TimeFrameInterval video_interval{TimeFrameIndex(1), TimeFrameIndex(2)};
1✔
206
            size_t count = 0;
1✔
207
            for (const auto& pair : timeframe_test_data.GetPointsInRange(video_interval,
1✔
208
                                                                         video_timeframe.get(), 
1✔
209
                                                                         data_timeframe.get())) {
6✔
210
                if (count == 0) {
3✔
211
                    REQUIRE(pair.time.getValue() == 2);
1✔
212
                    REQUIRE(pair.points.size() == 2);
1✔
213
                } else if (count == 1) {
2✔
214
                    REQUIRE(pair.time.getValue() == 3);
1✔
215
                    REQUIRE(pair.points.size() == 1);
1✔
216
                } else if (count == 2) {
1✔
217
                    REQUIRE(pair.time.getValue() == 4);
1✔
218
                    REQUIRE(pair.points.size() == 2);
1✔
219
                }
220
                count++;
3✔
221
            }
3✔
222
            REQUIRE(count == 3); // Should include converted times 2, 3, 4
1✔
223
        }
10✔
224

225
        SECTION("TimeFrameInterval overloads") {
9✔
226
            // Test the TimeFrameInterval convenience overload
227
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(20)};
1✔
228
            
229
            size_t count = 0;
1✔
230
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
4✔
231
                if (count == 0) {
3✔
232
                    REQUIRE(pair.time.getValue() == 10);
1✔
233
                    REQUIRE(pair.points.size() == 2);
1✔
234
                } else if (count == 1) {
2✔
235
                    REQUIRE(pair.time.getValue() == 15);
1✔
236
                    REQUIRE(pair.points.size() == 1);
1✔
237
                } else if (count == 2) {
1✔
238
                    REQUIRE(pair.time.getValue() == 20);
1✔
239
                    REQUIRE(pair.points.size() == 1);
1✔
240
                }
241
                count++;
3✔
242
            }
3✔
243
            REQUIRE(count == 3); // Should include times 10, 15, 20
1✔
244
        }
9✔
245

246
        SECTION("TimeFrameInterval with timeframe conversion") {
9✔
247
            // Create a separate point data instance for timeframe conversion test
248
            PointData timeframe_test_data;
1✔
249
            
250
            // Create source timeframe (video frames)
251
            std::vector<int> video_times = {0, 10, 20, 30, 40};  
3✔
252
            auto video_timeframe = std::make_shared<TimeFrame>(video_times);
1✔
253
            
254
            // Create target timeframe (data sampling)
255
            std::vector<int> data_times = {0, 5, 10, 15, 20, 25, 30, 35, 40}; 
3✔
256
            auto data_timeframe = std::make_shared<TimeFrame>(data_times);
1✔
257
            
258
            // Add data at target timeframe indices
259
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(2), points);  // At data timeframe index 2 (time=10)
1✔
260
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(3), more_points);  // At data timeframe index 3 (time=15)
1✔
261
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(4), points);  // At data timeframe index 4 (time=20)
1✔
262
            
263
            // Create interval in video timeframe (frames 1-2 covering times 10-20)
264
            TimeFrameInterval video_interval{TimeFrameIndex(1), TimeFrameIndex(2)};
1✔
265
            
266
            size_t count = 0;
1✔
267
            for (const auto& pair : timeframe_test_data.GetPointsInRange(video_interval,
1✔
268
                                                                         video_timeframe.get(), 
1✔
269
                                                                         data_timeframe.get())) {
6✔
270
                if (count == 0) {
3✔
271
                    REQUIRE(pair.time.getValue() == 2);
1✔
272
                    REQUIRE(pair.points.size() == 2);
1✔
273
                } else if (count == 1) {
2✔
274
                    REQUIRE(pair.time.getValue() == 3);
1✔
275
                    REQUIRE(pair.points.size() == 1);
1✔
276
                } else if (count == 2) {
1✔
277
                    REQUIRE(pair.time.getValue() == 4);
1✔
278
                    REQUIRE(pair.points.size() == 2);
1✔
279
                }
280
                count++;
3✔
281
            }
3✔
282
            REQUIRE(count == 3); // Should include converted times 2, 3, 4
1✔
283
        }
10✔
284
    }
17✔
285

286
    SECTION("Setting and getting image size") {
17✔
287
        ImageSize size{640, 480};
1✔
288
        point_data.setImageSize(size);
1✔
289

290
        auto retrieved_size = point_data.getImageSize();
1✔
291
        REQUIRE(retrieved_size.width == 640);
1✔
292
        REQUIRE(retrieved_size.height == 480);
1✔
293
    }
17✔
294

295
    SECTION("Overwriting points at multiple times") {
17✔
296
        std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20)};
3✔
297
        std::vector<std::vector<Point2D<float>>> points_vec = {points, more_points};
5✔
298

299
        point_data.overwritePointsAtTimes(times, points_vec);
1✔
300

301
        auto points_at_10 = point_data.getAtTime(TimeFrameIndex(10));
1✔
302
        auto points_at_20 = point_data.getAtTime(TimeFrameIndex(20));
1✔
303

304
        REQUIRE(points_at_10.size() == 2);
1✔
305
        REQUIRE(points_at_20.size() == 1);
1✔
306
    }
18✔
307

308
    SECTION("Getting times with points") {
17✔
309
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
310
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points);
1✔
311

312
        auto times = point_data.getTimesWithData();
1✔
313

314
        REQUIRE(times.size() == 2);
1✔
315
        
316
        // Convert to vector for easier testing or use iterators
317
        auto it = times.begin();
1✔
318
        REQUIRE(*it == TimeFrameIndex(10));
1✔
319
        ++it;
1✔
320
        REQUIRE(*it == TimeFrameIndex(20));
1✔
321
    }
17✔
322

323
    SECTION("Getting max points") {
17✔
324
        point_data.addPointsAtTime(TimeFrameIndex(10), points);      // 2 points
1✔
325
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points); // 1 point
1✔
326

327
        auto max_points = point_data.getMaxPoints();
1✔
328
        REQUIRE(max_points == 2);
1✔
329
    }
17✔
330
}
35✔
331

332
TEST_CASE("PointData - Entity ID handling in copy/move operations", "[points][data][entity][copy][move]") {
3✔
333
    PointData source_data;
3✔
334
    PointData target_data;
3✔
335

336
    // Setup test points
337
    Point2D<float> a{1.0f, 2.0f};
3✔
338
    Point2D<float> b{3.0f, 4.0f};
3✔
339
    Point2D<float> c{5.0f, 6.0f};
3✔
340

341
    // Add test points
342
    source_data.addAtTime(TimeFrameIndex(10), a);
3✔
343
    source_data.addAtTime(TimeFrameIndex(10), b);
3✔
344
    source_data.addAtTime(TimeFrameIndex(20), c);
3✔
345

346
    SECTION("Copy operations create new entity IDs") {
3✔
347
        // Get original entity IDs
348
        auto original_entity_ids = source_data.getAllEntityIds();
1✔
349
        REQUIRE(original_entity_ids.size() == 3);
1✔
350

351
        // Copy data
352
        TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(20)};
1✔
353
        source_data.copyTo(target_data, interval);
1✔
354

355
        // Get new entity IDs from target
356
        auto target_entity_ids = target_data.getAllEntityIds();
1✔
357
        REQUIRE(target_entity_ids.size() == 3);
1✔
358

359
        // Verify source entity IDs are unchanged
360
        auto source_entity_ids_after = source_data.getAllEntityIds();
1✔
361
        REQUIRE(source_entity_ids_after == original_entity_ids);
1✔
362
    }
4✔
363

364
    SECTION("Move operations create new entity IDs in target") {
3✔
365
        // Get original entity IDs
366
        auto original_entity_ids = source_data.getAllEntityIds();
1✔
367
        REQUIRE(original_entity_ids.size() == 3);
1✔
368

369
        // Move data
370
        TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(10)};
1✔
371
        source_data.moveTo(target_data, interval);
1✔
372

373
        // Get new entity IDs from target
374
        auto target_entity_ids = target_data.getAllEntityIds();
1✔
375
        REQUIRE(target_entity_ids.size() == 2); // 2 points at time 10
1✔
376

377
        // Verify source still has remaining data with original entity IDs
378
        auto remaining_entity_ids = source_data.getAllEntityIds();
1✔
379
        REQUIRE(remaining_entity_ids.size() == 1); // 1 point at time 20
1✔
380
    }
4✔
381

382
    SECTION("Entity ID consistency within time frames") {
3✔
383
        PointData pd;
1✔
384
        pd.addAtTime(TimeFrameIndex(10), a);
1✔
385
        pd.addAtTime(TimeFrameIndex(10), b);
1✔
386
        pd.addAtTime(TimeFrameIndex(30), a);
1✔
387
        pd.addAtTime(TimeFrameIndex(30), b);
1✔
388

389
        auto entity_ids_10 = pd.getEntityIdsAtTime(TimeFrameIndex(10));
1✔
390
        auto entity_ids_30 = pd.getEntityIdsAtTime(TimeFrameIndex(30));
1✔
391

392
        REQUIRE(entity_ids_10.size() == 2);
1✔
393
        REQUIRE(entity_ids_30.size() == 2);
1✔
394
    }
4✔
395
}
6✔
396

397
TEST_CASE("PointData - Copy and Move by EntityID", "[points][data][entity][copy][move][by_id]") {
9✔
398
    // Setup test points
399
    Point2D<float> p1{1.0f, 2.0f};
9✔
400
    Point2D<float> p2{3.0f, 4.0f};
9✔
401
    Point2D<float> p3{5.0f, 6.0f};
9✔
402
    Point2D<float> p4{7.0f, 8.0f};
9✔
403

404
    auto data_manager = std::make_unique<DataManager>();
9✔
405
    auto time_frame = std::make_shared<TimeFrame>(std::vector<int>{0, 10, 20, 30});
27✔
406
    data_manager->setTime(TimeKey("test_time"), time_frame);
9✔
407

408
    SECTION("Copy points by EntityID - basic functionality") {
9✔
409
        // Setup fresh source and target data
410
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
411
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
412

413
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
414
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
415

416
        // Add test points
417
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
418
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
419
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
420
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
421

422
        // Get entity IDs for testing
423
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
424
        REQUIRE(entity_ids_10.size() == 2);
1✔
425

426
        // Copy points from time 10 (2 points)
427
        std::unordered_set<EntityId> ids_set_10c(entity_ids_10.begin(), entity_ids_10.end());
3✔
428
        std::size_t points_copied = source_data->copyByEntityIds(*target_data, ids_set_10c);
1✔
429

430
        REQUIRE(points_copied == 2);
1✔
431

432
        // Verify source data is unchanged
433
        REQUIRE(source_data->getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
434
        REQUIRE(source_data->getAtTime(TimeFrameIndex(20)).size() == 1);
1✔
435
        REQUIRE(source_data->getAtTime(TimeFrameIndex(30)).size() == 1);
1✔
436

437
        // Verify target has copied data
438
        REQUIRE(target_data->getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
439
        REQUIRE(target_data->getAtTime(TimeFrameIndex(20)).size() == 0);
1✔
440
        REQUIRE(target_data->getAtTime(TimeFrameIndex(30)).size() == 0);
1✔
441

442
        // Verify target has new entity IDs
443
        auto target_entity_ids = target_data->getAllEntityIds();
1✔
444
        REQUIRE(target_entity_ids.size() == 2);
1✔
445
        REQUIRE(target_entity_ids != entity_ids_10);
1✔
446
    }
10✔
447

448
    SECTION("Copy points by EntityID - mixed times") {
9✔
449
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
450
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
451

452
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
453
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
454

455
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
456
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
457
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
458
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
459

460
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
461
        auto entity_ids_20 = source_data->getEntityIdsAtTime(TimeFrameIndex(20));
1✔
462
        REQUIRE(entity_ids_10.size() == 2);
1✔
463
        REQUIRE(entity_ids_20.size() == 1);
1✔
464

465
        std::vector<EntityId> mixed_entity_ids = {entity_ids_10[0], entity_ids_20[0]};
3✔
466
        std::unordered_set<EntityId> ids_set_mixedc(mixed_entity_ids.begin(), mixed_entity_ids.end());
3✔
467
        std::size_t points_copied = source_data->copyByEntityIds(*target_data, ids_set_mixedc);
1✔
468

469
        REQUIRE(points_copied == 2);
1✔
470
        REQUIRE(target_data->getAtTime(TimeFrameIndex(10)).size() == 1);
1✔
471
        REQUIRE(target_data->getAtTime(TimeFrameIndex(20)).size() == 1);
1✔
472
        REQUIRE(target_data->getAtTime(TimeFrameIndex(30)).size() == 0);
1✔
473
    }
10✔
474

475
    SECTION("Copy points by EntityID - non-existent EntityIDs") {
9✔
476
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
477
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
478

479
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
480
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
481

482
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
483
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
484
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
485
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
486

487
        std::vector<EntityId> fake_entity_ids = {99999, 88888};
3✔
488
        std::unordered_set<EntityId> ids_set_fakec(fake_entity_ids.begin(), fake_entity_ids.end());
3✔
489
        std::size_t points_copied = source_data->copyByEntityIds(*target_data, ids_set_fakec);
1✔
490

491
        REQUIRE(points_copied == 0);
1✔
492
        REQUIRE(target_data->getTimesWithData().empty());
1✔
493
    }
10✔
494

495
    SECTION("Copy points by EntityID - empty EntityID list") {
9✔
496
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
497
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
498

499
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
500
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
501

502
        std::vector<EntityId> empty_entity_ids;
1✔
503
        std::unordered_set<EntityId> ids_set_emptyc(empty_entity_ids.begin(), empty_entity_ids.end());
3✔
504
        std::size_t points_copied = source_data->copyByEntityIds(*target_data, ids_set_emptyc);
1✔
505

506
        REQUIRE(points_copied == 0);
1✔
507
        REQUIRE(target_data->getTimesWithData().empty());
1✔
508
    }
10✔
509

510
    SECTION("Move points by EntityID - basic functionality") {
9✔
511
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
512
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
513

514
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
515
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
516

517
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
518
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
519
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
520
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
521

522
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
523
        REQUIRE(entity_ids_10.size() == 2);
1✔
524

525
        std::unordered_set<EntityId> const ids_set_10(entity_ids_10.begin(), entity_ids_10.end());
3✔
526
        std::size_t points_moved = source_data->moveByEntityIds(*target_data, ids_set_10);
1✔
527

528
        REQUIRE(points_moved == 2);
1✔
529
        REQUIRE(source_data->getAtTime(TimeFrameIndex(10)).size() == 0);
1✔
530
        REQUIRE(source_data->getAtTime(TimeFrameIndex(20)).size() == 1);
1✔
531
        REQUIRE(source_data->getAtTime(TimeFrameIndex(30)).size() == 1);
1✔
532

533
        REQUIRE(target_data->getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
534
        REQUIRE(target_data->getAtTime(TimeFrameIndex(20)).size() == 0);
1✔
535
        REQUIRE(target_data->getAtTime(TimeFrameIndex(30)).size() == 0);
1✔
536

537
        auto target_entity_ids = target_data->getAllEntityIds();
1✔
538
        REQUIRE(target_entity_ids.size() == 2);
1✔
539
        REQUIRE(target_entity_ids == entity_ids_10);
1✔
540
    }
10✔
541

542
    SECTION("Move points by EntityID - mixed times") {
9✔
543
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
544
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
545

546
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
547
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
548

549
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
550
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
551
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
552
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
553

554
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
555
        auto entity_ids_20 = source_data->getEntityIdsAtTime(TimeFrameIndex(20));
1✔
556

557
        std::vector<EntityId> mixed_entity_ids = {entity_ids_10[0], entity_ids_20[0]};
3✔
558
        std::unordered_set<EntityId> const ids_set_mixed(mixed_entity_ids.begin(), mixed_entity_ids.end());
3✔
559
        std::size_t points_moved = source_data->moveByEntityIds(*target_data, ids_set_mixed);
1✔
560

561
        REQUIRE(points_moved == 2);
1✔
562
        REQUIRE(source_data->getAtTime(TimeFrameIndex(10)).size() == 1);
1✔
563
        REQUIRE(source_data->getAtTime(TimeFrameIndex(20)).size() == 0);
1✔
564
        REQUIRE(source_data->getAtTime(TimeFrameIndex(30)).size() == 1);
1✔
565

566
        REQUIRE(target_data->getAtTime(TimeFrameIndex(10)).size() == 1);
1✔
567
        REQUIRE(target_data->getAtTime(TimeFrameIndex(20)).size() == 1);
1✔
568
        REQUIRE(target_data->getAtTime(TimeFrameIndex(30)).size() == 0);
1✔
569
    }
10✔
570

571
    SECTION("Move points by EntityID - non-existent EntityIDs") {
9✔
572
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
573
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
574

575
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
576
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
577

578
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
579
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
580
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
581
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
582

583
        std::vector<EntityId> fake_entity_ids = {99999, 88888};
3✔
584
        std::unordered_set<EntityId> const ids_set_fake(fake_entity_ids.begin(), fake_entity_ids.end());
3✔
585
        std::size_t points_moved = source_data->moveByEntityIds(*target_data, ids_set_fake);
1✔
586

587
        REQUIRE(points_moved == 0);
1✔
588
        REQUIRE(target_data->getTimesWithData().empty());
1✔
589

590
        REQUIRE(source_data->getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
591
        REQUIRE(source_data->getAtTime(TimeFrameIndex(20)).size() == 1);
1✔
592
        REQUIRE(source_data->getAtTime(TimeFrameIndex(30)).size() == 1);
1✔
593
    }
10✔
594

595
    SECTION("Copy preserves point data integrity") {
9✔
596
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
597
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
598

599
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
600
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
601

602
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
603
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
604
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
605
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
606

607
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
608
        std::unordered_set<EntityId> ids_set_10c2(entity_ids_10.begin(), entity_ids_10.end());
3✔
609
        source_data->copyByEntityIds(*target_data, ids_set_10c2);
1✔
610

611
        auto source_points = source_data->getAtTime(TimeFrameIndex(10));
1✔
612
        auto target_points = target_data->getAtTime(TimeFrameIndex(10));
1✔
613

614
        REQUIRE(source_points.size() == target_points.size());
1✔
615
        for (size_t i = 0; i < source_points.size(); ++i) {
3✔
616
            REQUIRE(source_points[i].x == Catch::Approx(target_points[i].x));
2✔
617
            REQUIRE(source_points[i].y == Catch::Approx(target_points[i].y));
2✔
618
        }
619
    }
10✔
620

621
    SECTION("Move preserves point data integrity") {
9✔
622
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
623
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
624

625
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
626
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
627

628
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
629
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
630
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
631
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
632

633
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
634
        auto original_points = source_data->getAtTime(TimeFrameIndex(10));
1✔
635
        REQUIRE(original_points.size() == 2);
1✔
636

637
        std::unordered_set<EntityId> const ids_set_10b(entity_ids_10.begin(), entity_ids_10.end());
3✔
638
        source_data->moveByEntityIds(*target_data, ids_set_10b);
1✔
639

640
        auto target_points = target_data->getAtTime(TimeFrameIndex(10));
1✔
641
        REQUIRE(target_points.size() == 2);
1✔
642

643
        // Verify each original point is present in target (order may differ)
644
        for (auto const & sp : original_points) {
3✔
645
            bool found = false;
2✔
646
            for (auto const & tp : target_points) {
3✔
647
                if (sp.x == tp.x && sp.y == tp.y) { found = true; break; }
3✔
648
            }
649
            REQUIRE(found);
2✔
650
        }
651
    }
10✔
652
}
18✔
653

654
TEST_CASE("DM - PointData - Edge cases and error handling", "[points][data][error]") {
7✔
655
    PointData point_data;
7✔
656

657
    SECTION("Getting points at non-existent time") {
7✔
658
        auto points = point_data.getAtTime(TimeFrameIndex(999));
1✔
659
        REQUIRE(points.empty());
1✔
660
    }
8✔
661

662
    SECTION("Clearing points at non-existent time") {
7✔
663
        static_cast<void>(point_data.clearAtTime(TimeFrameIndex(42)));
1✔
664
        auto points = point_data.getAtTime(TimeFrameIndex(42));
1✔
665
        REQUIRE(points.empty());
1✔
666

667
        // Verify the time was NOT created
668
        bool found = false;
1✔
669
        for (const auto& pair : point_data.GetAllPointsAsRange()) {
1✔
UNCOV
670
            if (pair.time.getValue() == 42) {
×
UNCOV
671
                found = true;
×
UNCOV
672
                break;
×
673
            }
674
        }
×
675
        REQUIRE(found == false);
1✔
676
    }
8✔
677

678
    SECTION("Empty range with no data") {
7✔
679
        auto range = point_data.GetAllPointsAsRange();
1✔
680
        size_t count = 0;
1✔
681

682
        for (auto const& pair : range) {
1✔
UNCOV
683
            count++;
×
UNCOV
684
        }
×
685

686
        REQUIRE(count == 0);
1✔
687
    }
7✔
688

689
    SECTION("Adding empty points vector") {
7✔
690
        std::vector<Point2D<float>> empty_points;
1✔
691
        point_data.addPointsAtTime(TimeFrameIndex(10), empty_points);
1✔
692

693
        auto points = point_data.getAtTime(TimeFrameIndex(10));
1✔
694
        REQUIRE(points.empty());
1✔
695

696
        // Verify the time was created
697
        bool found = false;
1✔
698
        for (const auto& pair : point_data.GetAllPointsAsRange()) {
1✔
699
            if (pair.time.getValue() == 10) {
1✔
700
                found = true;
1✔
701
                break;
1✔
702
            }
703
        }
1✔
704
        REQUIRE(found);
1✔
705
    }
8✔
706

707
    SECTION("Overwriting points at times with mismatched vectors") {
7✔
708
        std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20), TimeFrameIndex(30)};
3✔
709
        std::vector<std::vector<Point2D<float>>> points = {
1✔
710
                {{1.0f, 2.0f}},
711
                {{3.0f, 4.0f}}
712
        }; // Only 2 elements
6✔
713

714
        // This should not modify the data
715
        point_data.overwritePointsAtTimes(times, points);
1✔
716

717
        auto range = point_data.GetAllPointsAsRange();
1✔
718
        size_t count = 0;
1✔
719
        for (auto const& pair : range) {
1✔
UNCOV
720
            count++;
×
UNCOV
721
        }
×
722

723
        // No points should be added since we had a size mismatch
724
        REQUIRE(count == 0);
1✔
725
    }
8✔
726

727
    SECTION("Multiple operations sequence") {
7✔
728
        Point2D<float> p1{1.0f, 2.0f};
1✔
729

730
        // Add, clear, add again to test internal state consistency
731
        point_data.addAtTime(TimeFrameIndex(5), p1);
1✔
732
        static_cast<void>(point_data.clearAtTime(TimeFrameIndex(5)));
1✔
733
        point_data.addAtTime(TimeFrameIndex(5), p1);
1✔
734

735
        auto points = point_data.getAtTime(TimeFrameIndex(5));
1✔
736
        REQUIRE(points.size() == 1);
1✔
737
        REQUIRE(points[0].x == Catch::Approx(1.0f));
1✔
738
    }
8✔
739

740
    SECTION("Construction from map") {
7✔
741
        std::map<TimeFrameIndex, std::vector<Point2D<float>>> map_data = {
1✔
742
                {TimeFrameIndex(10), {{1.0f, 2.0f}, {3.0f, 4.0f}}},
2✔
743
                {TimeFrameIndex(20), {{5.0f, 6.0f}}}
2✔
744
        };
7✔
745

746
        PointData point_data_from_map(map_data);
1✔
747

748
        auto points_at_10 = point_data_from_map.getAtTime(TimeFrameIndex(10));
1✔
749
        auto points_at_20 = point_data_from_map.getAtTime(TimeFrameIndex(20));
1✔
750

751
        REQUIRE(points_at_10.size() == 2);
1✔
752
        REQUIRE(points_at_20.size() == 1);
1✔
753
        REQUIRE(points_at_10[0].x == Catch::Approx(1.0f));
1✔
754
        REQUIRE(points_at_20[0].x == Catch::Approx(5.0f));
1✔
755
    }
8✔
756
}
25✔
757

758
TEST_CASE("DM - PointData - Copy and Move operations", "[points][data][copy][move]") {
17✔
759
    PointData source_data;
17✔
760
    PointData target_data;
17✔
761

762
    // Setup test data
763
    std::vector<Point2D<float>> points_10 = {{1.0f, 2.0f}, {3.0f, 4.0f}};
51✔
764
    std::vector<Point2D<float>> points_15 = {{5.0f, 6.0f}};
51✔
765
    std::vector<Point2D<float>> points_20 = {{7.0f, 8.0f}, {9.0f, 10.0f}, {11.0f, 12.0f}};
51✔
766
    std::vector<Point2D<float>> points_25 = {{13.0f, 14.0f}};
51✔
767

768
    source_data.addPointsAtTime(TimeFrameIndex(10), points_10);
17✔
769
    source_data.addPointsAtTime(TimeFrameIndex(15), points_15);
17✔
770
    source_data.addPointsAtTime(TimeFrameIndex(20), points_20);
17✔
771
    source_data.addPointsAtTime(TimeFrameIndex(25), points_25);
17✔
772

773
    SECTION("copyTo - time range operations") {
17✔
774
        SECTION("Copy entire range") {
5✔
775
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(25)};
1✔
776
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
777
            
778
            REQUIRE(copied == 7); // 2 + 1 + 3 + 1 = 7 points total
1✔
779
            
780
            // Verify all points were copied
781
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
782
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
783
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
784
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
785
            
786
            // Verify source data is unchanged
787
            REQUIRE(source_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
788
            REQUIRE(source_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
789
        }
5✔
790

791
        SECTION("Copy partial range") {
5✔
792
            TimeFrameInterval interval{TimeFrameIndex(15), TimeFrameIndex(20)};
1✔
793
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
794
            
795
            REQUIRE(copied == 4); // 1 + 3 = 4 points
1✔
796
            
797
            // Verify only points in range were copied
798
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).empty());
1✔
799
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
800
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
801
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).empty());
1✔
802
        }
5✔
803

804
        SECTION("Copy single time point") {
5✔
805
            TimeFrameInterval interval{TimeFrameIndex(20), TimeFrameIndex(20)};
1✔
806
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
807
            
808
            REQUIRE(copied == 3);
1✔
809
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
810
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).empty());
1✔
811
        }
5✔
812

813
        SECTION("Copy non-existent range") {
5✔
814
            TimeFrameInterval interval{TimeFrameIndex(100), TimeFrameIndex(200)};
1✔
815
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
816
            
817
            REQUIRE(copied == 0);
1✔
818
            REQUIRE(target_data.getTimesWithData().empty());
1✔
819
        }
5✔
820

821
        SECTION("Copy with invalid range (start > end)") {
5✔
822
            TimeFrameInterval interval{TimeFrameIndex(25), TimeFrameIndex(10)};
1✔
823
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
824
            
825
            REQUIRE(copied == 0);
1✔
826
            REQUIRE(target_data.getTimesWithData().empty());
1✔
827
        }
5✔
828
    }
17✔
829

830
    SECTION("copyTo - specific times vector operations") {
17✔
831
        SECTION("Copy multiple specific times") {
5✔
832
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20)};
3✔
833
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
834
            
835
            REQUIRE(copied == 5); // 2 + 3 = 5 points
1✔
836
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
837
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).empty());
1✔
838
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
839
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).empty());
1✔
840
        }
6✔
841

842
        SECTION("Copy times in non-sequential order") {
5✔
843
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(25), TimeFrameIndex(10), TimeFrameIndex(15)}; // Non-sequential
3✔
844
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
845
            
846
            REQUIRE(copied == 4); // 1 + 2 + 1 = 4 points
1✔
847
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
848
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
849
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
850
        }
6✔
851

852
        SECTION("Copy with duplicate times in vector") {
5✔
853
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(10), TimeFrameIndex(20)}; // Duplicate time
3✔
854
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
855
            
856
            // Should copy 10 twice and 20 once
857
            REQUIRE(copied == 7); // 2 + 2 + 3 = 7 points (10 copied twice)
1✔
858
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 4); // 2 + 2 = 4 (added twice)
1✔
859
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
860
        }
6✔
861

862
        SECTION("Copy non-existent times") {
5✔
863
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(100), TimeFrameIndex(200), TimeFrameIndex(300)};
3✔
864
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
865
            
866
            REQUIRE(copied == 0);
1✔
867
            REQUIRE(target_data.getTimesWithData().empty());
1✔
868
        }
6✔
869

870
        SECTION("Copy mixed existent and non-existent times") {
5✔
871
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(100), TimeFrameIndex(20), TimeFrameIndex(200)};
3✔
872
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
873
            
874
            REQUIRE(copied == 5); // Only times 10 and 20 exist
1✔
875
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
876
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
877
        }
6✔
878
    }
17✔
879

880
    SECTION("moveTo - time range operations") {
17✔
881
        SECTION("Move entire range") {
3✔
882
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(25)};
1✔
883
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
884
            
885
            REQUIRE(moved == 7); // 2 + 1 + 3 + 1 = 7 points total
1✔
886
            
887
            // Verify all points were moved to target
888
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
889
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
890
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
891
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
892
            
893
            // Verify source data is now empty
894
            REQUIRE(source_data.getTimesWithData().empty());
1✔
895
        }
3✔
896

897
        SECTION("Move partial range") {
3✔
898
            TimeFrameInterval interval{TimeFrameIndex(15), TimeFrameIndex(20)};
1✔
899
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
900
            
901
            REQUIRE(moved == 4); // 1 + 3 = 4 points
1✔
902
            
903
            // Verify only points in range were moved
904
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
905
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
906
            
907
            // Verify source still has data outside the range
908
            REQUIRE(source_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
909
            REQUIRE(source_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
910
            REQUIRE(source_data.getAtTime(TimeFrameIndex(15)).empty());
1✔
911
            REQUIRE(source_data.getAtTime(TimeFrameIndex(20)).empty());
1✔
912
        }
3✔
913

914
        SECTION("Move single time point") {
3✔
915
            TimeFrameInterval interval{TimeFrameIndex(20), TimeFrameIndex(20)};
1✔
916
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
917
            
918
            REQUIRE(moved == 3);
1✔
919
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
920
            
921
            // Verify only time 20 was removed from source
922
            REQUIRE(source_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
923
            REQUIRE(source_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
924
            REQUIRE(source_data.getAtTime(TimeFrameIndex(20)).empty());
1✔
925
            REQUIRE(source_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
926
        }
3✔
927
    }
17✔
928

929
    SECTION("moveTo - specific times vector operations") {
17✔
930
        SECTION("Move multiple specific times") {
2✔
931
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20)};
3✔
932
            std::size_t moved = source_data.moveTo(target_data, times);
1✔
933
            
934
            REQUIRE(moved == 5); // 2 + 3 = 5 points
1✔
935
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
936
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
937
            
938
            // Verify moved times are cleared from source
939
            REQUIRE(source_data.getAtTime(TimeFrameIndex(10)).empty());
1✔
940
            REQUIRE(source_data.getAtTime(TimeFrameIndex(20)).empty());
1✔
941
            // But other times remain
942
            REQUIRE(source_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
943
            REQUIRE(source_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
944
        }
3✔
945

946
        SECTION("Move times in non-sequential order") {
2✔
947
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(25), TimeFrameIndex(10), TimeFrameIndex(15)}; // Non-sequential
3✔
948
            std::size_t moved = source_data.moveTo(target_data, times);
1✔
949
            
950
            REQUIRE(moved == 4); // 1 + 2 + 1 = 4 points
1✔
951
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
952
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
953
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
954
            
955
            // Only time 20 should remain in source
956
            REQUIRE(source_data.getTimesWithData().size() == 1);
1✔
957
            REQUIRE(source_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
958
        }
3✔
959
    }
17✔
960

961
    SECTION("Copy/Move to target with existing data") {
17✔
962
        // Add some existing data to target
963
        std::vector<Point2D<float>> existing_points = {{100.0f, 200.0f}};
6✔
964
        target_data.addPointsAtTime(TimeFrameIndex(10), existing_points);
2✔
965

966
        SECTION("Copy to existing time adds points") {
2✔
967
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(10)};
1✔
968
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
969
            
970
            REQUIRE(copied == 2);
1✔
971
            // Should have existing point plus copied points
972
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 3); // 1 existing + 2 copied
1✔
973
        }
2✔
974

975
        SECTION("Move to existing time adds points") {
2✔
976
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(10)};
1✔
977
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
978
            
979
            REQUIRE(moved == 2);
1✔
980
            // Should have existing point plus moved points
981
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 3); // 1 existing + 2 moved
1✔
982
            
983
            // Source should no longer have points at time 10
984
            REQUIRE(source_data.getAtTime(TimeFrameIndex(10)).empty());
1✔
985
        }
2✔
986
    }
19✔
987
}
34✔
988

989
TEST_CASE("DM - PointData - Image scaling", "[points][data][scaling]") {
3✔
990
    PointData point_data;
3✔
991
    
992
    // Setup test data with known coordinates
993
    std::vector<Point2D<float>> points = {{100.0f, 200.0f}, {300.0f, 400.0f}};
9✔
994
    point_data.addPointsAtTime(TimeFrameIndex(10), points);
3✔
995
    
996
    SECTION("Scaling from known size") {
3✔
997
        // Set initial image size
998
        ImageSize initial_size{640, 480};
1✔
999
        point_data.setImageSize(initial_size);
1✔
1000
        
1001
        // Scale to larger size
1002
        ImageSize new_size{1280, 960};
1✔
1003
        point_data.changeImageSize(new_size);
1✔
1004
        
1005
        auto scaled_points = point_data.getAtTime(TimeFrameIndex(10));
1✔
1006
        
1007
        // Points should be scaled by factor of 2
1008
        REQUIRE(scaled_points[0].x == Catch::Approx(200.0f));
1✔
1009
        REQUIRE(scaled_points[0].y == Catch::Approx(400.0f));
1✔
1010
        REQUIRE(scaled_points[1].x == Catch::Approx(600.0f));
1✔
1011
        REQUIRE(scaled_points[1].y == Catch::Approx(800.0f));
1✔
1012
        
1013
        // Image size should be updated
1014
        auto current_size = point_data.getImageSize();
1✔
1015
        REQUIRE(current_size.width == 1280);
1✔
1016
        REQUIRE(current_size.height == 960);
1✔
1017
    }
4✔
1018
    
1019
    SECTION("Scaling with no initial size set") {
3✔
1020
        // Try to scale without setting initial size
1021
        ImageSize new_size{1280, 960};
1✔
1022
        point_data.changeImageSize(new_size);
1✔
1023
        
1024
        // Points should remain unchanged since no initial size was set
1025
        auto unchanged_points = point_data.getAtTime(TimeFrameIndex(10));
1✔
1026
        REQUIRE(unchanged_points[0].x == Catch::Approx(100.0f));
1✔
1027
        REQUIRE(unchanged_points[0].y == Catch::Approx(200.0f));
1✔
1028
        
1029
        // But image size should now be set
1030
        auto current_size = point_data.getImageSize();
1✔
1031
        REQUIRE(current_size.width == 1280);
1✔
1032
        REQUIRE(current_size.height == 960);
1✔
1033
    }
4✔
1034
    
1035
    SECTION("Scaling to same size does nothing") {
3✔
1036
        ImageSize size{640, 480};
1✔
1037
        point_data.setImageSize(size);
1✔
1038
        
1039
        // Scale to same size
1040
        point_data.changeImageSize(size);
1✔
1041
        
1042
        // Points should remain unchanged
1043
        auto unchanged_points = point_data.getAtTime(TimeFrameIndex(10));
1✔
1044
        REQUIRE(unchanged_points[0].x == Catch::Approx(100.0f));
1✔
1045
        REQUIRE(unchanged_points[0].y == Catch::Approx(200.0f));
1✔
1046
    }
4✔
1047
}
6✔
1048

1049
TEST_CASE("PointData - Timeframe conversion", "[points][data][timeframe]") {
4✔
1050
    PointData point_data;
4✔
1051
    
1052
    // Setup test data
1053
    std::vector<Point2D<float>> points = {{100.0f, 200.0f}, {300.0f, 400.0f}};
12✔
1054
    point_data.addPointsAtTime(TimeFrameIndex(10), points);
4✔
1055
    point_data.addPointsAtTime(TimeFrameIndex(20), points);
4✔
1056
    
1057
    SECTION("Same timeframe returns original data") {
4✔
1058
        // Create a single timeframe
1059
        std::vector<int> times = {5, 10, 15, 20, 25};
3✔
1060
        auto timeframe = std::make_shared<TimeFrame>(times);
1✔
1061
        
1062
        // Query with same source and target timeframe
1063
        auto result = point_data.getAtTime(TimeFrameIndex(10), timeframe.get(), timeframe.get());
1✔
1064
        
1065
        REQUIRE(result.size() == 2);
1✔
1066
        REQUIRE(result[0].x == Catch::Approx(100.0f));
1✔
1067
        REQUIRE(result[0].y == Catch::Approx(200.0f));
1✔
1068
    }
5✔
1069
    
1070
    SECTION("Different timeframes with conversion") {
4✔
1071
        // Create source timeframe (e.g., video frames)
1072
        std::vector<int> video_times = {0, 10, 20, 30, 40};  // Video at 1 Hz
3✔
1073
        auto video_timeframe = std::make_shared<TimeFrame>(video_times);
1✔
1074
        
1075
        // Create target timeframe (e.g., data sampling at higher rate)
1076
        std::vector<int> data_times = {0, 5, 10, 15, 20, 25, 30, 35, 40}; // Data at 2 Hz
3✔
1077
        auto data_timeframe = std::make_shared<TimeFrame>(data_times);
1✔
1078
        
1079
        // Clear existing data and add data at the correct indices for the target timeframe
1080
        PointData test_point_data;
1✔
1081
        
1082
        // Video frame 1 (time=10) should map to data_timeframe index 2 (time=10)
1083
        // Video frame 2 (time=20) should map to data_timeframe index 4 (time=20)
1084
        test_point_data.addPointsAtTime(TimeFrameIndex(2), points);  // At data timeframe index 2 (time=10)
1✔
1085
        test_point_data.addPointsAtTime(TimeFrameIndex(4), points);  // At data timeframe index 4 (time=20)
1✔
1086
        
1087
        // Query video frame 1 (time=10) which should map to data index 2 (time=10)
1088
        auto result = test_point_data.getAtTime(TimeFrameIndex(1), 
1✔
1089
                                                video_timeframe.get(), 
1✔
1090
                                                data_timeframe.get());
2✔
1091
        
1092
        REQUIRE(result.size() == 2);
1✔
1093
        REQUIRE(result[0].x == Catch::Approx(100.0f));
1✔
1094
        REQUIRE(result[0].y == Catch::Approx(200.0f));
1✔
1095
    }
5✔
1096
    
1097
    SECTION("Timeframe conversion with no matching data") {
4✔
1098
        // Create timeframes where conversion maps to non-existent data
1099
        std::vector<int> video_times = {0, 5, 10};
3✔
1100
        auto video_timeframe = std::make_shared<TimeFrame>(video_times);
1✔
1101
        
1102
        std::vector<int> data_times = {0, 3, 7, 15, 25};
3✔
1103
        auto data_timeframe = std::make_shared<TimeFrame>(data_times);
1✔
1104
        
1105
        // Create a separate point data instance for this test
1106
        PointData test_point_data;
1✔
1107
        test_point_data.addPointsAtTime(TimeFrameIndex(3), points);  // At data timeframe index 3 (time=15)
1✔
1108
        
1109
        // Query video frame 1 (time=5) which should map to data timeframe index 1 (time=3, closest to 5)
1110
        // Since we only have data at index 3, this should return empty
1111
        auto result = test_point_data.getAtTime(TimeFrameIndex(1), video_timeframe.get(), data_timeframe.get());
1✔
1112
        
1113
        // Should return empty since we don't have data at the converted index
1114
        REQUIRE(result.empty());
1✔
1115
    }
5✔
1116
    
1117
    SECTION("Null timeframe handling") {
4✔
1118
        std::vector<int> times = {5, 10, 15, 20, 25};
3✔
1119
        auto valid_timeframe = std::make_shared<TimeFrame>(times);
1✔
1120
        
1121
        // This should still work since the function should handle null pointers gracefully
1122
        // by falling back to the original behavior
1123
        auto result = point_data.getAtTime(TimeFrameIndex(10), nullptr, valid_timeframe.get());
1✔
1124
        
1125
        // The behavior when timeframes are null might depend on getTimeIndexForSeries implementation
1126
        // For now, let's check that it doesn't crash and returns some result
1127
        // The exact behavior would depend on the TimeFrame implementation
1128
    }
5✔
1129
}
8✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc