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

paulmthompson / WhiskerToolbox / 17733471381

15 Sep 2025 12:43PM UTC coverage: 72.1% (+0.4%) from 71.744%
17733471381

push

github

paulmthompson
fix optional missing include on windows

37727 of 52326 relevant lines covered (72.1%)

1297.48 hits per line

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

99.65
/src/WhiskerToolbox/TableViewerWidget/TableViewerWidget.test.cpp
1
#include <catch2/catch_approx.hpp>
2
#include <catch2/catch_test_macros.hpp>
3

4
#include "PaginatedTableModel.hpp"
5
#include "TableViewerWidget.hpp"
6

7
// DataManager and TableView related includes
8
#include "DataManager.hpp"
9
#include "DataManager/DigitalTimeSeries/Digital_Event_Series.hpp"
10
#include "DataManager/DigitalTimeSeries/Digital_Interval_Series.hpp"
11
#include "DataManager/TimeFrame/TimeFrame.hpp"
12
#include "DataManager/TimeFrame/interval_data.hpp"
13
#include "DataManager/utils/TableView/ComputerRegistry.hpp"
14
#include "DataManager/utils/TableView/TableInfo.hpp"
15
#include "DataManager/utils/TableView/TableRegistry.hpp"
16
#include "DataManager/utils/TableView/adapters/DataManagerExtension.h"
17
#include "DataManager/utils/TableView/computers/AnalogSliceGathererComputer.h"
18
#include "DataManager/utils/TableView/computers/EventInIntervalComputer.h"
19
#include "DataManager/utils/TableView/core/ExecutionPlan.h"
20
#include "DataManager/utils/TableView/core/TableView.h"
21
#include "DataManager/utils/TableView/core/TableViewBuilder.h"
22
#include "DataManager/utils/TableView/interfaces/IEventSource.h"
23
#include "DataManager/utils/TableView/interfaces/IIntervalSource.h"
24
#include "DataManager/utils/TableView/interfaces/IRowSelector.h"
25

26
// Analog data
27
#include "AnalogTimeSeries/Analog_Time_Series.hpp"
28
// Line data sampling (multi-output)
29
#include "DataManager/Lines/Line_Data.hpp"
30
#include "DataManager/utils/TableView/computers/LineSamplingMultiComputer.h"
31

32
// Qt includes for widget testing
33
#include <QAbstractItemModel>
34
#include <QApplication>
35
#include <QTableView>
36

37
#include <algorithm>
38
#include <memory>
39
#include <numeric>
40
#include <vector>
41

42
/**
43
 * @brief Test fixture for TableViewerWidget that creates test data similar to EventInIntervalComputer tests
44
 * 
45
 * This fixture provides:
46
 * - DataManager with TimeFrames and test data
47
 * - TableRegistry access
48
 * - Methods to create TableView objects for testing
49
 * - Mock event and interval data for realistic testing
50
 */
51
class TableViewerWidgetTestFixture {
52
protected:
53
    TableViewerWidgetTestFixture() {
13✔
54
        // Initialize Qt application if not already done
55
        if (!QApplication::instance()) {
13✔
56
            int argc = 0;
13✔
57
            char ** argv = nullptr;
13✔
58
            app = std::make_unique<QApplication>(argc, argv);
13✔
59
        }
60

61
        // Initialize the DataManager
62
        m_data_manager = std::make_unique<DataManager>();
13✔
63

64
        // Populate with test data
65
        populateWithTestData();
13✔
66
    }
13✔
67

68
    ~TableViewerWidgetTestFixture() = default;
13✔
69

70
    /**
71
     * @brief Get the DataManager instance
72
     */
73
    DataManager & getDataManager() { return *m_data_manager; }
8✔
74
    DataManager const & getDataManager() const { return *m_data_manager; }
75
    DataManager * getDataManagerPtr() { return m_data_manager.get(); }
5✔
76

77
    /**
78
     * @brief Get the TableRegistry from DataManager
79
     */
80
    TableRegistry & getTableRegistry() { return *m_data_manager->getTableRegistry(); }
1✔
81

82
    /**
83
     * @brief Get DataManagerExtension for building tables
84
     */
85
    std::shared_ptr<DataManagerExtension> getDataManagerExtension() {
12✔
86
        if (!m_data_manager_extension) {
12✔
87
            m_data_manager_extension = std::make_shared<DataManagerExtension>(*m_data_manager);
10✔
88
        }
89
        return m_data_manager_extension;
12✔
90
    }
91

92
    /**
93
     * @brief Create a sample TableView for testing
94
     * @return Shared pointer to a TableView with test data
95
     */
96
    std::shared_ptr<TableView> createSampleTableView() {
3✔
97
        auto dme = getDataManagerExtension();
3✔
98

99
        // Get test data sources
100
        auto neuron1_source = dme->getEventSource("Neuron1Spikes");
9✔
101
        auto neuron2_source = dme->getEventSource("Neuron2Spikes");
9✔
102
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
9✔
103

104
        if (!neuron1_source || !neuron2_source || !behavior_source) {
3✔
105
            return nullptr;
×
106
        }
107

108
        // Create row selector from behavior intervals
109
        auto behavior_time_frame = m_data_manager->getTime(TimeKey("behavior_time"));
3✔
110
        auto behavior_intervals = behavior_source->getIntervalsInRange(
3✔
111
                TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
3✔
112

113
        std::vector<TimeFrameInterval> row_intervals;
3✔
114
        for (auto const & interval: behavior_intervals) {
15✔
115
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
12✔
116
        }
117

118
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
3✔
119

120
        // Build TableView with multiple columns
121
        TableViewBuilder builder(dme);
3✔
122
        builder.setRowSelector(std::move(row_selector));
3✔
123

124
        // Add different column types for testing
125
        builder.addColumn<bool>("Neuron1_Present",
15✔
126
                                std::make_unique<EventInIntervalComputer<bool>>(neuron1_source,
9✔
127
                                                                                EventOperation::Presence, "Neuron1Spikes"));
6✔
128

129
        builder.addColumn<int>("Neuron1_Count",
15✔
130
                               std::make_unique<EventInIntervalComputer<int>>(neuron1_source,
9✔
131
                                                                              EventOperation::Count, "Neuron1Spikes"));
6✔
132

133
        builder.addColumn<bool>("Neuron2_Present",
15✔
134
                                std::make_unique<EventInIntervalComputer<bool>>(neuron2_source,
9✔
135
                                                                                EventOperation::Presence, "Neuron2Spikes"));
6✔
136

137
        builder.addColumn<int>("Neuron2_Count",
15✔
138
                               std::make_unique<EventInIntervalComputer<int>>(neuron2_source,
9✔
139
                                                                              EventOperation::Count, "Neuron2Spikes"));
6✔
140

141
        builder.addColumn<std::vector<float>>("Neuron1_Times",
15✔
142
                                              std::make_unique<EventInIntervalComputer<std::vector<float>>>(neuron1_source,
9✔
143
                                                                                                            EventOperation::Gather, "Neuron1Spikes"));
6✔
144

145
        // Build and return the table
146
        TableView table = builder.build();
3✔
147
        return std::make_shared<TableView>(std::move(table));
3✔
148
    }
3✔
149

150
    /**
151
     * @brief Create column infos for table configuration testing
152
     */
153
    std::vector<ColumnInfo> createSampleColumnInfos() {
2✔
154
        std::vector<ColumnInfo> column_infos;
2✔
155

156
        // Create column info for each test column
157
        ColumnInfo neuron1_present("Neuron1_Present", "Presence of Neuron1 spikes",
2✔
158
                                   "Neuron1Spikes", "Event Presence");
18✔
159
        neuron1_present.outputType = typeid(bool);
2✔
160
        neuron1_present.outputTypeName = "bool";
2✔
161
        column_infos.push_back(std::move(neuron1_present));
2✔
162

163
        ColumnInfo neuron1_count("Neuron1_Count", "Count of Neuron1 spikes",
2✔
164
                                 "Neuron1Spikes", "Event Count");
18✔
165
        neuron1_count.outputType = typeid(int);
2✔
166
        neuron1_count.outputTypeName = "int";
2✔
167
        column_infos.push_back(std::move(neuron1_count));
2✔
168

169
        ColumnInfo neuron2_present("Neuron2_Present", "Presence of Neuron2 spikes",
2✔
170
                                   "Neuron2Spikes", "Event Presence");
18✔
171
        neuron2_present.outputType = typeid(bool);
2✔
172
        neuron2_present.outputTypeName = "bool";
2✔
173
        column_infos.push_back(std::move(neuron2_present));
2✔
174

175
        ColumnInfo neuron2_count("Neuron2_Count", "Count of Neuron2 spikes",
2✔
176
                                 "Neuron2Spikes", "Event Count");
18✔
177
        neuron2_count.outputType = typeid(int);
2✔
178
        neuron2_count.outputTypeName = "int";
2✔
179
        column_infos.push_back(std::move(neuron2_count));
2✔
180

181
        ColumnInfo neuron1_times("Neuron1_Times", "Spike times for Neuron1",
2✔
182
                                 "Neuron1Spikes", "Event Gather");
18✔
183
        neuron1_times.outputType = typeid(std::vector<float>);
2✔
184
        neuron1_times.outputTypeName = "std::vector<float>";
2✔
185
        neuron1_times.isVectorType = true;
2✔
186
        neuron1_times.elementType = typeid(float);
2✔
187
        neuron1_times.elementTypeName = "float";
2✔
188
        column_infos.push_back(std::move(neuron1_times));
2✔
189

190
        return column_infos;
4✔
191
    }
2✔
192

193
    /**
194
     * @brief Create a row selector for table configuration testing
195
     */
196
    std::unique_ptr<IRowSelector> createSampleRowSelector() {
4✔
197
        auto dme = getDataManagerExtension();
4✔
198
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
12✔
199

200
        if (!behavior_source) {
4✔
201
            return nullptr;
×
202
        }
203

204
        auto behavior_time_frame = m_data_manager->getTime(TimeKey("behavior_time"));
4✔
205
        auto behavior_intervals = behavior_source->getIntervalsInRange(
4✔
206
                TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
4✔
207

208
        std::vector<TimeFrameInterval> row_intervals;
4✔
209
        for (auto const & interval: behavior_intervals) {
20✔
210
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
16✔
211
        }
212

213
        return std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
4✔
214
    }
4✔
215

216
private:
217
    std::unique_ptr<DataManager> m_data_manager;
218
    std::shared_ptr<DataManagerExtension> m_data_manager_extension;
219
    std::unique_ptr<QApplication> app;
220

221
    /**
222
     * @brief Populate the DataManager with test data (similar to EventInIntervalComputer tests)
223
     */
224
    void populateWithTestData() {
13✔
225
        createTimeFrames();
13✔
226
        createBehaviorIntervals();
13✔
227
        createSpikeEvents();
13✔
228
    }
13✔
229

230
    /**
231
     * @brief Create TimeFrame objects for different data streams
232
     */
233
    void createTimeFrames() {
13✔
234
        // Create "behavior_time" timeframe: 0 to 100 (101 points)
235
        std::vector<int> behavior_time_values(101);
39✔
236
        std::iota(behavior_time_values.begin(), behavior_time_values.end(), 0);
13✔
237
        auto behavior_time_frame = std::make_shared<TimeFrame>(behavior_time_values);
13✔
238
        m_data_manager->setTime(TimeKey("behavior_time"), behavior_time_frame, true);
13✔
239

240
        // Create "spike_time" timeframe: 0, 2, 4, 6, ..., 100 (51 points)
241
        std::vector<int> spike_time_values;
13✔
242
        spike_time_values.reserve(51);
13✔
243
        for (int i = 0; i <= 50; ++i) {
676✔
244
            spike_time_values.push_back(i * 2);
663✔
245
        }
246
        auto spike_time_frame = std::make_shared<TimeFrame>(spike_time_values);
13✔
247
        m_data_manager->setTime(TimeKey("spike_time"), spike_time_frame, true);
13✔
248
    }
26✔
249

250
    /**
251
     * @brief Create behavior intervals (row intervals for testing)
252
     */
253
    void createBehaviorIntervals() {
13✔
254
        auto behavior_intervals = std::make_shared<DigitalIntervalSeries>();
13✔
255

256
        // Create 4 behavior periods for testing
257
        behavior_intervals->addEvent(TimeFrameIndex(10), TimeFrameIndex(25));// Exploration 1
13✔
258
        behavior_intervals->addEvent(TimeFrameIndex(30), TimeFrameIndex(40));// Rest
13✔
259
        behavior_intervals->addEvent(TimeFrameIndex(50), TimeFrameIndex(70));// Exploration 2
13✔
260
        behavior_intervals->addEvent(TimeFrameIndex(80), TimeFrameIndex(95));// Social
13✔
261

262
        m_data_manager->setData<DigitalIntervalSeries>("BehaviorPeriods", behavior_intervals, TimeKey("behavior_time"));
39✔
263
    }
26✔
264

265
    /**
266
     * @brief Create spike event data (event data for testing)
267
     */
268
    void createSpikeEvents() {
13✔
269
        // Create spike train for Neuron1
270
        std::vector<float> neuron1_spikes = {
13✔
271
                1.0f, 6.0f, 7.0f, 11.0f, 16.0f, 26.0f, 27.0f, 34.0f, 41.0f, 45.0f};
39✔
272
        auto neuron1_series = std::make_shared<DigitalEventSeries>(neuron1_spikes);
13✔
273
        m_data_manager->setData<DigitalEventSeries>("Neuron1Spikes", neuron1_series, TimeKey("spike_time"));
39✔
274

275
        // Create spike train for Neuron2
276
        std::vector<float> neuron2_spikes = {
13✔
277
                0.0f, 1.0f, 2.0f, 5.0f, 6.0f, 8.0f, 9.0f, 15.0f, 16.0f, 18.0f,
278
                25.0f, 26.0f, 28.0f, 29.0f, 33.0f, 34.0f, 40.0f, 41.0f, 42.0f, 45.0f, 46.0f};
39✔
279
        auto neuron2_series = std::make_shared<DigitalEventSeries>(neuron2_spikes);
13✔
280
        m_data_manager->setData<DigitalEventSeries>("Neuron2Spikes", neuron2_series, TimeKey("spike_time"));
39✔
281
    }
26✔
282
};
283

284
TEST_CASE_METHOD(TableViewerWidgetTestFixture, "TableViewerWidget - Basic Functionality", "[TableViewerWidget]") {
3✔
285

286
    SECTION("Create widget and verify initial state") {
3✔
287
        TableViewerWidget widget;
1✔
288

289
        // Verify initial state
290
        REQUIRE(!widget.hasTable());
1✔
291
        REQUIRE(widget.getTableName().isEmpty());
1✔
292

293
        // Widget should be properly constructed
294
        REQUIRE(widget.isWidgetType());
1✔
295
    }
4✔
296

297
    SECTION("Set table view and verify row/column counts") {
3✔
298
        TableViewerWidget widget;
1✔
299

300
        // Create a sample table
301
        auto table_view = createSampleTableView();
1✔
302
        REQUIRE(table_view != nullptr);
1✔
303

304
        // Get original table dimensions
305
        size_t original_row_count = table_view->getRowCount();
1✔
306
        size_t original_column_count = table_view->getColumnCount();
1✔
307

308
        // Set the table in the widget
309
        widget.setTableView(table_view, "Test Table");
1✔
310

311
        // Verify widget state
312
        REQUIRE(widget.hasTable());
1✔
313
        REQUIRE(widget.getTableName() == "Test Table");
1✔
314

315
        // Access the internal model to verify dimensions
316
        // Note: We need to access the QTableView to get the model
317
        auto * table_view_widget = widget.findChild<QTableView *>();
1✔
318
        REQUIRE(table_view_widget != nullptr);
1✔
319

320
        auto * model = table_view_widget->model();
1✔
321
        REQUIRE(model != nullptr);
1✔
322

323
        // Verify the model has the correct dimensions
324
        REQUIRE(static_cast<size_t>(model->columnCount()) == original_column_count);
1✔
325
        REQUIRE(static_cast<size_t>(model->rowCount()) == original_row_count);
1✔
326

327
        // Verify expected dimensions based on our test data
328
        REQUIRE(original_row_count == 4);   // 4 behavior periods
1✔
329
        REQUIRE(original_column_count == 5);// 5 columns we added
1✔
330
    }
4✔
331

332
    SECTION("Set table configuration and verify row/column counts") {
3✔
333
        TableViewerWidget widget;
1✔
334

335
        // Create configuration components
336
        auto row_selector = createSampleRowSelector();
1✔
337
        auto column_infos = createSampleColumnInfos();
1✔
338
        auto data_manager = std::shared_ptr<DataManager>(getDataManagerPtr(), [](DataManager *) {});
1✔
339

340
        REQUIRE(row_selector != nullptr);
1✔
341
        REQUIRE(!column_infos.empty());
1✔
342

343
        // Get expected dimensions before setting
344
        size_t expected_columns = column_infos.size();
1✔
345

346
        // Calculate expected rows by counting intervals in the row selector
347
        // For our test data, we should have 4 behavior periods
348
        size_t expected_rows = 4;
1✔
349

350
        // Set the table configuration
351
        widget.setTableConfiguration(std::move(row_selector), std::move(column_infos),
1✔
352
                                     data_manager, "Configuration Test");
353

354
        // Verify widget state
355
        REQUIRE(widget.hasTable());
1✔
356
        REQUIRE(widget.getTableName() == "Configuration Test");
1✔
357

358
        // Access the internal model to verify dimensions
359
        auto * table_view_widget = widget.findChild<QTableView *>();
1✔
360
        REQUIRE(table_view_widget != nullptr);
1✔
361

362
        auto * model = table_view_widget->model();
1✔
363
        REQUIRE(model != nullptr);
1✔
364

365
        // Verify the model has the correct dimensions
366
        REQUIRE(static_cast<size_t>(model->columnCount()) == expected_columns);
1✔
367
        REQUIRE(static_cast<size_t>(model->rowCount()) == expected_rows);
1✔
368

369
        // Verify expected dimensions
370
        REQUIRE(expected_columns == 5);// 5 columns we defined
1✔
371
        REQUIRE(expected_rows == 4);   // 4 behavior periods
1✔
372
    }
4✔
373
}
3✔
374

375
TEST_CASE_METHOD(TableViewerWidgetTestFixture, "TableViewerWidget - Display boolean column (Event Presence)", "[TableViewerWidget][BoolDisplay]") {
1✔
376
    auto dme = getDataManagerExtension();
1✔
377
    REQUIRE(dme != nullptr);
1✔
378

379
    // Row selector: behavior periods from fixture (4 rows)
380
    auto row_selector = createSampleRowSelector();
1✔
381
    REQUIRE(row_selector != nullptr);
1✔
382

383
    // Event source: Neuron1Spikes (from fixture)
384
    auto n1 = dme->getEventSource("Neuron1Spikes");
3✔
385
    REQUIRE(n1 != nullptr);
1✔
386

387
    // Build table with a single boolean presence column
388
    TableViewBuilder builder(dme);
1✔
389
    builder.setRowSelector(std::move(row_selector));
1✔
390
    builder.addColumn<bool>(
5✔
391
            "N1_Present",
392
            std::make_unique<EventInIntervalComputer<bool>>(n1, EventOperation::Presence, "Neuron1Spikes"));
2✔
393

394
    TableView table = builder.build();
1✔
395
    auto table_view = std::make_shared<TableView>(std::move(table));
1✔
396

397
    // Show in widget and verify
398
    TableViewerWidget widget;
1✔
399
    widget.setTableView(table_view, "Bool Column Test");
1✔
400
    REQUIRE(widget.hasTable());
1✔
401

402
    auto * tv = widget.findChild<QTableView *>();
1✔
403
    REQUIRE(tv != nullptr);
1✔
404
    auto * model = tv->model();
1✔
405
    REQUIRE(model != nullptr);
1✔
406

407
    REQUIRE(model->rowCount() == 4);
1✔
408
    REQUIRE(model->columnCount() == 1);
1✔
409

410
    // Presence may be true across all behavior periods depending on timeframe conversion.
411
    QString expected[4] = {QStringLiteral("true"), QStringLiteral("true"), QStringLiteral("true"), QStringLiteral("true")};
10✔
412
    for (int r = 0; r < 4; ++r) {
5✔
413
        QString v = model->data(model->index(r, 0), Qt::DisplayRole).toString();
4✔
414
        REQUIRE(v == expected[r]);
4✔
415
    }
4✔
416
}
7✔
417

418
TEST_CASE_METHOD(TableViewerWidgetTestFixture, "TableViewerWidget - Display vector column (AnalogSliceGathererComputer)", "[TableViewerWidget][VectorDisplay]") {
1✔
419
    auto & dm = getDataManager();
1✔
420
    auto dme = getDataManagerExtension();
1✔
421

422
    // Timeframe 0..9
423
    std::vector<int> time_vals(10);
3✔
424
    std::iota(time_vals.begin(), time_vals.end(), 0);
1✔
425
    auto tf = std::make_shared<TimeFrame>(time_vals);
1✔
426
    dm.setTime(TimeKey("vec_time"), tf, true);
1✔
427

428
    // Analog 0..9 at each index
429
    std::vector<float> vals(10);
3✔
430
    std::iota(vals.begin(), vals.end(), 0.0f);
1✔
431
    std::vector<TimeFrameIndex> tix;
1✔
432
    tix.reserve(10);
1✔
433
    for (int i = 0; i < 10; ++i) tix.emplace_back(i);
11✔
434
    auto ats = std::make_shared<AnalogTimeSeries>(vals, tix);
1✔
435
    dm.setData<AnalogTimeSeries>("VecAnalog", ats, TimeKey("vec_time"));
3✔
436

437
    // Two intervals: [2,4] and [6,8]
438
    std::vector<TimeFrameInterval> intervals;
1✔
439
    intervals.emplace_back(TimeFrameIndex(2), TimeFrameIndex(4));
1✔
440
    intervals.emplace_back(TimeFrameIndex(6), TimeFrameIndex(8));
1✔
441
    auto row_selector = std::make_unique<IntervalSelector>(intervals, tf);
1✔
442

443
    // Build table with AnalogSliceGathererComputer<double>
444
    TableViewBuilder builder(dme);
1✔
445
    builder.setRowSelector(std::move(row_selector));
1✔
446
    auto analog_src = dme->getAnalogSource("VecAnalog");
3✔
447
    REQUIRE(analog_src != nullptr);
1✔
448
    builder.addColumn<std::vector<double>>("Slices", std::make_unique<AnalogSliceGathererComputer<std::vector<double>>>(analog_src, "VecAnalog"));
3✔
449
    TableView table = builder.build();
1✔
450
    auto table_view = std::make_shared<TableView>(std::move(table));
1✔
451

452
    // Show in widget
453
    TableViewerWidget widget;
1✔
454
    widget.setTableView(table_view, "Vector Column Test");
1✔
455
    REQUIRE(widget.hasTable());
1✔
456

457
    auto * tv = widget.findChild<QTableView *>();
1✔
458
    REQUIRE(tv != nullptr);
1✔
459
    auto * model = tv->model();
1✔
460
    REQUIRE(model != nullptr);
1✔
461

462
    REQUIRE(model->rowCount() == 2);
1✔
463
    REQUIRE(model->columnCount() == 1);
1✔
464

465
    // Verify formatted display strings (comma-separated, 3 decimals)
466
    auto row0 = model->data(model->index(0, 0), Qt::DisplayRole).toString();
1✔
467
    auto row1 = model->data(model->index(1, 0), Qt::DisplayRole).toString();
1✔
468
    REQUIRE(row0 == QString("2.000,3.000,4.000"));
1✔
469
    REQUIRE(row1 == QString("6.000,7.000,8.000"));
1✔
470
}
2✔
471

472
TEST_CASE_METHOD(TableViewerWidgetTestFixture, "TableViewerWidget - Pagination with analog timestamps", "[TableViewerWidget][Pagination][Analog]") {
1✔
473
    std::cout << "CTEST_FULL_OUTPUT" << std::endl;
1✔
474

475
    // Parameters
476
    int const num_rows = 2500;// triggers pagination
1✔
477
    int const page_size = 64; // small to force multiple pages
1✔
478

479
    // Create a monotonic analog signal: value(t) = t for t in [0, num_rows)
480
    auto time_frame = std::make_shared<TimeFrame>([&] {
3✔
481
        std::vector<int> t(num_rows);
3✔
482
        std::iota(t.begin(), t.end(), 0);
1✔
483
        return t;
1✔
484
    }());
3✔
485
    getDataManager().setTime(TimeKey("time"), time_frame, true);
1✔
486

487
    std::vector<float> values(static_cast<size_t>(num_rows));
3✔
488
    std::iota(values.begin(), values.end(), 0.0f);
1✔
489
    std::vector<TimeFrameIndex> indices;
1✔
490
    for (int i = 0; i < num_rows; ++i) indices.push_back(TimeFrameIndex(i));
2,501✔
491
    auto ats = std::make_shared<AnalogTimeSeries>(values, indices);
1✔
492
    getDataManager().setData<AnalogTimeSeries>("Iota", ats, TimeKey("time"));
3✔
493

494
    // Build Timestamp row selector 0..num_rows-1 in the same timeframe
495
    std::vector<TimeFrameIndex> row_timestamps(indices.begin(), indices.end());
3✔
496
    auto row_selector = std::make_unique<TimestampSelector>(std::move(row_timestamps), time_frame);
1✔
497

498
    // One column sampling analog at timestamps
499
    std::vector<ColumnInfo> column_infos;
1✔
500
    ColumnInfo info("IotaValue", "Analog sample at timestamp", "analog:Iota", "Timestamp Value");
9✔
501
    info.outputType = typeid(double);
1✔
502
    info.outputTypeName = "double";
1✔
503
    column_infos.push_back(std::move(info));
1✔
504

505
    // Wire into widget using pagination-backed API
506
    TableViewerWidget widget;
1✔
507
    widget.setPageSize(static_cast<size_t>(page_size));
1✔
508
    auto data_manager = std::shared_ptr<DataManager>(getDataManagerPtr(), [](DataManager *) {});
1✔
509
    widget.setTableConfiguration(std::move(row_selector), std::move(column_infos), data_manager, "Analog Pagination");
1✔
510

511
    REQUIRE(widget.hasTable());
1✔
512

513
    auto * table_view_widget = widget.findChild<QTableView *>();
1✔
514
    REQUIRE(table_view_widget != nullptr);
1✔
515
    auto * model = table_view_widget->model();
1✔
516
    REQUIRE(model != nullptr);
1✔
517

518
    REQUIRE(model->rowCount() == num_rows);
1✔
519
    REQUIRE(model->columnCount() == 1);
1✔
520

521
    auto expectValueAt = [&](int row) {
1✔
522
        auto idx = model->index(row, 0);
7✔
523
        REQUIRE(idx.isValid());
7✔
524
        auto v = model->data(idx, Qt::DisplayRole).toString();
7✔
525
        // "Timestamp Value" returns double formatted to 3 decimals by model
526
        QString expected = QString::number(static_cast<double>(row), 'f', 3);
7✔
527
        REQUIRE(v == expected);
7✔
528
    };
14✔
529

530
    // Probe around page boundaries
531
    expectValueAt(0);
1✔
532
    expectValueAt(page_size - 1);
1✔
533
    expectValueAt(page_size);
1✔
534
    expectValueAt(page_size * 2);
1✔
535
    expectValueAt(250);
1✔
536
    expectValueAt(1024);
1✔
537
    expectValueAt(2048);
1✔
538
}
2✔
539

540
TEST_CASE_METHOD(TableViewerWidgetTestFixture, "TableViewerWidget - Resize increases visible rows (lazy fill)", "[TableViewerWidget][Pagination][Resize]") {
1✔
541
    // Build small analog table similar to previous test
542
    int const num_rows = 1000;
1✔
543
    int const page_size = 64;
1✔
544

545
    auto time_frame = std::make_shared<TimeFrame>([&] {
3✔
546
        std::vector<int> t(num_rows);
3✔
547
        std::iota(t.begin(), t.end(), 0);
1✔
548
        return t;
1✔
549
    }());
3✔
550
    getDataManager().setTime(TimeKey("time_resize"), time_frame, true);
1✔
551

552
    std::vector<float> values(static_cast<size_t>(num_rows));
3✔
553
    std::iota(values.begin(), values.end(), 0.0f);
1✔
554
    std::vector<TimeFrameIndex> indices;
1✔
555
    indices.reserve(static_cast<size_t>(num_rows));
1✔
556
    for (int i = 0; i < num_rows; ++i) indices.push_back(TimeFrameIndex(i));
1,001✔
557
    auto ats = std::make_shared<AnalogTimeSeries>(values, indices);
1✔
558
    getDataManager().setData<AnalogTimeSeries>("IotaResize", ats, TimeKey("time_resize"));
3✔
559

560
    std::vector<TimeFrameIndex> row_timestamps(indices.begin(), indices.end());
3✔
561
    auto row_selector = std::make_unique<TimestampSelector>(std::move(row_timestamps), time_frame);
1✔
562

563
    std::vector<ColumnInfo> column_infos;
1✔
564
    ColumnInfo info("Val", "Analog at t", "analog:IotaResize", "Timestamp Value");
9✔
565
    info.outputType = typeid(double);
1✔
566
    info.outputTypeName = "double";
1✔
567
    column_infos.push_back(std::move(info));
1✔
568

569
    TableViewerWidget widget;
1✔
570
    widget.setPageSize(static_cast<size_t>(page_size));
1✔
571
    auto data_manager = std::shared_ptr<DataManager>(getDataManagerPtr(), [](DataManager *) {});
1✔
572
    widget.setTableConfiguration(std::move(row_selector), std::move(column_infos), data_manager, "Resize Test");
1✔
573

574
    auto * table_view = widget.findChild<QTableView *>();
1✔
575
    REQUIRE(table_view != nullptr);
1✔
576
    auto * model = table_view->model();
1✔
577
    REQUIRE(model != nullptr);
1✔
578

579
    // Initial small size to limit visible rows
580
    widget.resize(400, 200);
1✔
581
    widget.show();
1✔
582
    QApplication::processEvents();
1✔
583

584
    // Determine top/visible region
585
    QModelIndex top_idx = table_view->indexAt(table_view->rect().topLeft());
1✔
586
    int top_row = top_idx.isValid() ? top_idx.row() : 0;
1✔
587
    // Sample a bottom-ish row within current viewport (heuristic)
588
    int sample_row_before = std::min(top_row + 20, num_rows - 1);
1✔
589
    auto before_idx = model->index(sample_row_before, 0);
1✔
590
    QVariant before_val = model->data(before_idx, Qt::DisplayRole);
1✔
591
    REQUIRE(before_val.isValid());
1✔
592

593
    // Resize larger: more rows exposed
594
    widget.resize(400, 800);
1✔
595
    QApplication::processEvents();
1✔
596

597
    // New region likely exposes more rows; check one that should newly appear
598
    QModelIndex new_top_idx = table_view->indexAt(table_view->rect().topLeft());
1✔
599
    int new_top_row = new_top_idx.isValid() ? new_top_idx.row() : 0;
1✔
600
    int sample_row_after = std::min(new_top_row + 60, num_rows - 1);
1✔
601
    auto after_idx = model->index(sample_row_after, 0);
1✔
602
    auto after_val = model->data(after_idx, Qt::DisplayRole);
1✔
603

604
    // Accept either expected formatted value or temporarily empty while mini-page materializes
605
    QString expected = QString::number(static_cast<double>(sample_row_after), 'f', 3);
1✔
606
    REQUIRE((!after_val.isValid() || after_val.toString().isEmpty() || after_val.toString() == expected));
1✔
607

608
    // After processing events again, value should materialize
609
    QApplication::processEvents();
1✔
610
    after_val = model->data(after_idx, Qt::DisplayRole);
1✔
611
    REQUIRE(after_val.isValid());
1✔
612
    REQUIRE(after_val.toString() == expected);
1✔
613
}
2✔
614

615
TEST_CASE_METHOD(TableViewerWidgetTestFixture, "TableViewerWidget - Table Registry Integration", "[TableViewerWidget][Registry]") {
1✔
616

617
    SECTION("Verify table registry provides correct table access") {
1✔
618
        auto & registry = getTableRegistry();
1✔
619
        auto & dm = getDataManager();
1✔
620

621
        // Create a table using the registry's computers
622
        auto dme = getDataManagerExtension();
1✔
623

624
        // Build a table using registry components
625
        auto row_selector = createSampleRowSelector();
1✔
626
        REQUIRE(row_selector != nullptr);
1✔
627

628
        TableViewBuilder builder(dme);
1✔
629
        builder.setRowSelector(std::move(row_selector));
1✔
630

631
        // Get neuron sources
632
        auto neuron1_source = dme->getEventSource("Neuron1Spikes");
3✔
633
        auto neuron2_source = dme->getEventSource("Neuron2Spikes");
3✔
634
        REQUIRE(neuron1_source != nullptr);
1✔
635
        REQUIRE(neuron2_source != nullptr);
1✔
636

637
        // Use registry to create computers
638
        auto & computer_registry = registry.getComputerRegistry();
1✔
639

640
        std::map<std::string, std::string> empty_params;
1✔
641

642
        auto presence_computer1 = computer_registry.createTypedComputer<bool>(
1✔
643
                "Event Presence", neuron1_source, empty_params);
3✔
644
        auto count_computer1 = computer_registry.createTypedComputer<int>(
1✔
645
                "Event Count", neuron1_source, empty_params);
3✔
646
        auto presence_computer2 = computer_registry.createTypedComputer<bool>(
1✔
647
                "Event Presence", neuron2_source, empty_params);
3✔
648
        auto count_computer2 = computer_registry.createTypedComputer<int>(
1✔
649
                "Event Count", neuron2_source, empty_params);
3✔
650

651
        REQUIRE(presence_computer1 != nullptr);
1✔
652
        REQUIRE(count_computer1 != nullptr);
1✔
653
        REQUIRE(presence_computer2 != nullptr);
1✔
654
        REQUIRE(count_computer2 != nullptr);
1✔
655

656
        // Add columns using registry-created computers
657
        builder.addColumn("Registry_N1_Present", std::move(presence_computer1));
3✔
658
        builder.addColumn("Registry_N1_Count", std::move(count_computer1));
3✔
659
        builder.addColumn("Registry_N2_Present", std::move(presence_computer2));
3✔
660
        builder.addColumn("Registry_N2_Count", std::move(count_computer2));
3✔
661

662
        // Build the table
663
        TableView registry_table = builder.build();
1✔
664
        auto table_view = std::make_shared<TableView>(std::move(registry_table));
1✔
665

666
        // Test with TableViewerWidget
667
        TableViewerWidget widget;
1✔
668
        widget.setTableView(table_view, "Registry Table");
1✔
669

670
        // Verify dimensions
671
        REQUIRE(widget.hasTable());
1✔
672
        REQUIRE(widget.getTableName() == "Registry Table");
1✔
673

674
        auto * table_view_widget = widget.findChild<QTableView *>();
1✔
675
        REQUIRE(table_view_widget != nullptr);
1✔
676

677
        auto * model = table_view_widget->model();
1✔
678
        REQUIRE(model != nullptr);
1✔
679

680
        // Verify correct number of columns and rows
681
        REQUIRE(model->columnCount() == 4);// 4 registry-created columns
1✔
682
        REQUIRE(model->rowCount() == 4);   // 4 behavior periods
1✔
683

684
        // Verify these match the original table
685
        REQUIRE(static_cast<size_t>(model->columnCount()) == table_view->getColumnCount());
1✔
686
        REQUIRE(static_cast<size_t>(model->rowCount()) == table_view->getRowCount());
1✔
687
    }
2✔
688
}
1✔
689

690
TEST_CASE_METHOD(TableViewerWidgetTestFixture, "TableViewerWidget - Data Consistency", "[TableViewerWidget][Data]") {
1✔
691

692
    SECTION("Verify table data matches source table") {
1✔
693
        TableViewerWidget widget;
1✔
694

695
        // Create a sample table
696
        auto table_view = createSampleTableView();
1✔
697
        REQUIRE(table_view != nullptr);
1✔
698

699
        // Get some reference data from the original table
700
        auto original_columns = table_view->getColumnNames();
1✔
701
        auto original_neuron1_present = table_view->getColumnValues<bool>("Neuron1_Present");
3✔
702
        auto original_neuron1_count = table_view->getColumnValues<int>("Neuron1_Count");
3✔
703

704
        // Set the table in the widget
705
        widget.setTableView(table_view, "Data Test");
1✔
706

707
        // Access the model to verify data consistency
708
        auto * table_view_widget = widget.findChild<QTableView *>();
1✔
709
        REQUIRE(table_view_widget != nullptr);
1✔
710

711
        auto * model = table_view_widget->model();
1✔
712
        REQUIRE(model != nullptr);
1✔
713

714
        // Verify column headers match
715
        for (int col = 0; col < model->columnCount(); ++col) {
6✔
716
            QString header = model->headerData(col, Qt::Horizontal, Qt::DisplayRole).toString();
5✔
717
            REQUIRE(!header.isEmpty());
5✔
718
        }
5✔
719

720
        // Verify we can access data without errors
721
        for (int row = 0; row < model->rowCount(); ++row) {
5✔
722
            for (int col = 0; col < model->columnCount(); ++col) {
24✔
723
                QVariant data = model->data(model->index(row, col), Qt::DisplayRole);
20✔
724
                // Data should be valid (even if empty string for complex types)
725
                REQUIRE(data.isValid());
20✔
726
            }
20✔
727
        }
728

729
        // Verify dimensions match expectations
730
        REQUIRE(model->rowCount() == static_cast<int>(original_neuron1_present.size()));
1✔
731
        REQUIRE(model->rowCount() == static_cast<int>(original_neuron1_count.size()));
1✔
732
        REQUIRE(model->columnCount() == static_cast<int>(original_columns.size()));
1✔
733
    }
2✔
734
}
1✔
735

736
TEST_CASE_METHOD(TableViewerWidgetTestFixture, "TableViewerWidget - Widget Lifecycle", "[TableViewerWidget][Lifecycle]") {
2✔
737

738
    SECTION("Clear table and verify state reset") {
2✔
739
        TableViewerWidget widget;
1✔
740

741
        // Set a table
742
        auto table_view = createSampleTableView();
1✔
743
        widget.setTableView(table_view, "Test Table");
1✔
744

745
        // Verify table is set
746
        REQUIRE(widget.hasTable());
1✔
747
        REQUIRE(widget.getTableName() == "Test Table");
1✔
748

749
        // Clear the table
750
        widget.clearTable();
1✔
751

752
        // Verify state is reset
753
        REQUIRE(!widget.hasTable());
1✔
754
        REQUIRE(widget.getTableName().isEmpty());
1✔
755

756
        // Model should still exist but be empty or reset
757
        auto * table_view_widget = widget.findChild<QTableView *>();
1✔
758
        REQUIRE(table_view_widget != nullptr);
1✔
759

760
        auto * model = table_view_widget->model();
1✔
761
        // Model might be null or have 0 rows/columns after clearing
762
        if (model != nullptr) {
1✔
763
            // If model exists, it should have no data
764
            REQUIRE((model->rowCount() == 0 || model->columnCount() == 0));
1✔
765
        }
766
    }
3✔
767

768
    SECTION("Set page size and verify it's applied") {
2✔
769
        TableViewerWidget widget;
1✔
770

771
        // Set page size before setting table
772
        widget.setPageSize(100);
1✔
773

774
        // Create configuration components
775
        auto row_selector = createSampleRowSelector();
1✔
776
        auto column_infos = createSampleColumnInfos();
1✔
777
        auto data_manager = std::shared_ptr<DataManager>(getDataManagerPtr(), [](DataManager *) {});
1✔
778

779
        // Set table configuration
780
        widget.setTableConfiguration(std::move(row_selector), std::move(column_infos),
1✔
781
                                     data_manager, "Page Size Test");
782

783
        // Verify table is set and working
784
        REQUIRE(widget.hasTable());
1✔
785

786
        // With our small test dataset (4 rows), page size shouldn't affect visibility
787
        auto * table_view_widget = widget.findChild<QTableView *>();
1✔
788
        REQUIRE(table_view_widget != nullptr);
1✔
789

790
        auto * model = table_view_widget->model();
1✔
791
        REQUIRE(model != nullptr);
1✔
792
        REQUIRE(model->rowCount() == 4);// All rows should be visible
1✔
793
    }
3✔
794
}
2✔
795

796
TEST_CASE_METHOD(TableViewerWidgetTestFixture, "TableViewerWidget - Pagination with large table", "[TableViewerWidget][Pagination]") {
1✔
797
    // Build a large pagination-backed configuration: 2500 rows and 1000 columns
798

799
    std::cout << "CTEST_FULL_OUTPUT" << std::endl;
1✔
800

801
    int const num_rows = 2500;
1✔
802
    int const num_columns = 10;
1✔
803

804
    auto dme = getDataManagerExtension();
1✔
805
    REQUIRE(dme != nullptr);
1✔
806

807
    // Create many intervals (rows) using existing behavior_time timeframe (0..100)
808
    auto behavior_time_frame = getDataManager().getTime(TimeKey("behavior_time"));
1✔
809
    std::vector<TimeFrameInterval> row_intervals;
1✔
810
    row_intervals.reserve(num_rows);
1✔
811
    for (size_t i = 0; i < num_rows; ++i) {
2,501✔
812
        auto start = TimeFrameIndex(static_cast<int>(i % 100));
2,500✔
813
        auto end = TimeFrameIndex(static_cast<int>((i % 100) + 1));
2,500✔
814
        row_intervals.emplace_back(start, end);
2,500✔
815
    }
816

817
    auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
818

819
    // Create column infos alternating presence and count from event source "Neuron1Spikes"
820
    std::vector<ColumnInfo> column_infos;
1✔
821
    column_infos.reserve(num_columns);
1✔
822
    for (int i = 0; i < num_columns; ++i) {
11✔
823
        if (i % 2 == 0) {
10✔
824
            ColumnInfo info("Col_" + std::to_string(i), "Presence", "Neuron1Spikes", "Event Presence");
35✔
825
            info.outputType = typeid(bool);
5✔
826
            info.outputTypeName = "bool";
5✔
827
            column_infos.push_back(std::move(info));
5✔
828
        } else {
5✔
829
            ColumnInfo info("Col_" + std::to_string(i), "Count", "Neuron1Spikes", "Event Count");
35✔
830
            info.outputType = typeid(int);
5✔
831
            info.outputTypeName = "int";
5✔
832
            column_infos.push_back(std::move(info));
5✔
833
        }
5✔
834
    }
835

836
    // Use the widget's pagination-backed API
837
    TableViewerWidget widget;
1✔
838
    widget.setPageSize(64);
1✔
839
    auto data_manager = std::shared_ptr<DataManager>(getDataManagerPtr(), [](DataManager *) {});
1✔
840
    widget.setTableConfiguration(std::move(row_selector), std::move(column_infos), data_manager, "Large Pagination Table");
1✔
841

842
    REQUIRE(widget.hasTable());
1✔
843

844
    auto * table_view_widget = widget.findChild<QTableView *>();
1✔
845
    REQUIRE(table_view_widget != nullptr);
1✔
846
    auto * base_model = table_view_widget->model();
1✔
847
    REQUIRE(base_model != nullptr);
1✔
848

849
    // Confirm dimensions
850
    REQUIRE(base_model->rowCount() == num_rows);
1✔
851
    REQUIRE(base_model->columnCount() == num_columns);
1✔
852

853
    // Access underlying model to track materialized pages
854
    auto * model = dynamic_cast<PaginatedTableModel *>(base_model);
1✔
855
    REQUIRE(model != nullptr);
1✔
856

857
    auto accessRow = [&](int row) {
1✔
858
        for (int col = 0; col < std::min(5, base_model->columnCount()); ++col) {
54✔
859
            auto idx = base_model->index(row, col);
45✔
860
            REQUIRE(idx.isValid());
45✔
861
            auto val = base_model->data(idx, Qt::DisplayRole);
45✔
862
            REQUIRE(val.isValid());
45✔
863
        }
45✔
864
    };
9✔
865

866
    size_t initial_pages = model->getMaterializedPageCount();
1✔
867

868
    // Trigger access across multiple page boundaries
869
    accessRow(0);   // page 0
1✔
870
    accessRow(63);  // still page 0
1✔
871
    accessRow(64);  // page 1
1✔
872
    accessRow(127); // page 1
1✔
873
    accessRow(128); // page 2
1✔
874
    accessRow(512); // deeper page
1✔
875
    accessRow(1024);// deeper page
1✔
876
    accessRow(2048);// deeper page
1✔
877

878
    size_t pages_after = model->getMaterializedPageCount();
1✔
879
    REQUIRE(pages_after >= initial_pages + 4);
1✔
880

881
    // Also simulate scrolling the view and then accessing data at the new top
882
    table_view_widget->scrollTo(base_model->index(1500, 0), QAbstractItemView::PositionAtTop);
1✔
883
    accessRow(1500);
1✔
884
}
2✔
885

886
TEST_CASE_METHOD(TableViewerWidgetTestFixture, "TableViewerWidget - LineSampling with entity expansion", "[TableViewerWidget][LineSampling][EntityExpansion]") {
1✔
887
    // Build LineData with multiple entities per timestamp and verify TableViewerWidget reflects expansion
888
    auto & dm = getDataManager();
1✔
889
    auto dme = getDataManagerExtension();
1✔
890

891
    // Timeframe with three timestamps 0,1,2
892
    std::vector<int> tvals = {0, 1, 2};
3✔
893
    auto tf = std::make_shared<TimeFrame>(tvals);
1✔
894
    dm.setTime(TimeKey("line_time"), tf, true);
1✔
895

896
    // Create LineData: t=0 -> 1 line, t=1 -> 2 lines, t=2 -> 3 lines
897
    auto lines = std::make_shared<LineData>();
1✔
898
    lines->setTimeFrame(tf);
1✔
899
    // Helper lambdas to add simple lines
900
    auto add_h = [&](int t, float x0, float x1, float y) {
1✔
901
        std::vector<float> xs = {x0, x1};
9✔
902
        std::vector<float> ys = {y, y};
9✔
903
        lines->addAtTime(TimeFrameIndex(t), xs, ys, false);
3✔
904
    };
6✔
905
    auto add_v = [&](int t, float x, float y0, float y1) {
1✔
906
        std::vector<float> xs = {x, x};
9✔
907
        std::vector<float> ys = {y0, y1};
9✔
908
        lines->addAtTime(TimeFrameIndex(t), xs, ys, false);
3✔
909
    };
6✔
910
    // t=0: 1 line
911
    add_h(0, 0.0f, 10.0f, 0.0f);
1✔
912
    // t=1: 2 lines
913
    add_h(1, 0.0f, 10.0f, 1.0f);
1✔
914
    add_v(1, 5.0f, 0.0f, 10.0f);
1✔
915
    // t=2: 3 lines
916
    add_h(2, 0.0f, 10.0f, 2.0f);
1✔
917
    add_v(2, 2.0f, 0.0f, 10.0f);
1✔
918
    add_v(2, 8.0f, 0.0f, 10.0f);
1✔
919

920
    dm.setData<LineData>("LineMulti", lines, TimeKey("line_time"));
3✔
921

922
    // Row selector: timestamps [0,1,2]
923
    std::vector<TimeFrameIndex> ts = {TimeFrameIndex(0), TimeFrameIndex(1), TimeFrameIndex(2)};
3✔
924
    auto row_selector = std::make_unique<TimestampSelector>(ts, tf);
1✔
925

926
    // Build a table with LineSamplingMultiComputer (segments=2 -> 3 positions -> 6 columns)
927
    TableViewBuilder builder(dme);
1✔
928
    builder.setRowSelector(std::move(row_selector));
1✔
929
    auto line_src = dme->getLineSource("LineMulti");
3✔
930
    REQUIRE(line_src != nullptr);
1✔
931
    int const segments = 2;
1✔
932
    auto multi = std::make_unique<LineSamplingMultiComputer>(line_src, std::string{"LineMulti"}, tf, segments);
3✔
933
    builder.addColumns<double>("Line", std::move(multi));
3✔
934

935
    TableView table = builder.build();
1✔
936
    auto table_view = std::make_shared<TableView>(std::move(table));
1✔
937

938
    // Show in TableViewerWidget
939
    TableViewerWidget widget;
1✔
940
    widget.setTableView(table_view, "Line Entity Expansion");
1✔
941
    REQUIRE(widget.hasTable());
1✔
942

943
    auto * tv = widget.findChild<QTableView *>();
1✔
944
    REQUIRE(tv != nullptr);
1✔
945
    auto * model = tv->model();
1✔
946
    REQUIRE(model != nullptr);
1✔
947

948
    // Expect 6 columns (x,y at positions 0.0, 0.5, 1.0)
949
    REQUIRE(model->columnCount() == 2 * (segments + 1));
1✔
950
    // Column count should exceed number of timestamps in the selector (3)
951
    REQUIRE(model->columnCount() > static_cast<int>(ts.size()));
1✔
952

953
    // Rows should reflect entity expansion: 1 + 2 + 3 = 6 > selector size (3)
954
    REQUIRE(model->rowCount() == 6);
1✔
955
    REQUIRE(model->rowCount() > static_cast<int>(ts.size()));
1✔
956

957
    // Also verify model mirrors underlying table dimensions
958
    REQUIRE(model->columnCount() == static_cast<int>(table_view->getColumnCount()));
1✔
959
    REQUIRE(model->rowCount() == static_cast<int>(table_view->getRowCount()));
1✔
960
}
2✔
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