Warning, file /education/labplot/tests/spreadsheet/SpreadsheetTest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     File                 : SpreadsheetTest.cpp
0003     Project              : LabPlot
0004     Description          : Tests for the Spreadsheet
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2020-2023 Alexander Semke <alexander.semke@web.de>
0007     SPDX-FileCopyrightText: 2022 Stefan Gerlach <stefan.gerlach@uni.kn>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "SpreadsheetTest.h"
0013 #include "backend/core/Project.h"
0014 #include "backend/core/datatypes/DateTime2StringFilter.h"
0015 #include "backend/datasources/filters/VectorBLFFilter.h"
0016 #include "backend/spreadsheet/Spreadsheet.h"
0017 #include "backend/spreadsheet/SpreadsheetModel.h"
0018 #include "backend/spreadsheet/StatisticsSpreadsheet.h"
0019 #include "commonfrontend/ProjectExplorer.h"
0020 #include "commonfrontend/spreadsheet/SpreadsheetView.h"
0021 #include "kdefrontend/dockwidgets/SpreadsheetDock.h"
0022 #include "kdefrontend/spreadsheet/FlattenColumnsDialog.h"
0023 #include "kdefrontend/spreadsheet/SearchReplaceWidget.h"
0024 
0025 #ifdef HAVE_VECTOR_BLF
0026 #include <Vector/BLF.h>
0027 #endif
0028 
0029 #include <QClipboard>
0030 #include <QModelIndex>
0031 #include <QUndoStack>
0032 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
0033 #include <QRandomGenerator>
0034 #endif
0035 
0036 #include <gsl/gsl_math.h>
0037 
0038 //**********************************************************
0039 //****************** Copy&Paste tests **********************
0040 //**********************************************************
0041 
0042 //**********************************************************
0043 //********** Handling of different columns modes ***********
0044 //**********************************************************
0045 /*!
0046    insert two columns with float values into an empty spreadsheet
0047 */
0048 void SpreadsheetTest::testCopyPasteColumnMode00() {
0049     QLocale::setDefault(QLocale::C); // . as decimal separator
0050     Spreadsheet sheet(QStringLiteral("test"), false);
0051     sheet.setColumnCount(2);
0052     sheet.setRowCount(100);
0053 
0054     const QString str = QStringLiteral("10.0 100.0\n20.0 200.0");
0055     QApplication::clipboard()->setText(str);
0056 
0057     SpreadsheetView view(&sheet, false);
0058     view.pasteIntoSelection();
0059 
0060     // spreadsheet size
0061     QCOMPARE(sheet.columnCount(), 2);
0062     QCOMPARE(sheet.rowCount(), 100);
0063 
0064     // column modes
0065     QCOMPARE(sheet.column(0)->columnMode(), AbstractColumn::ColumnMode::Double);
0066     QCOMPARE(sheet.column(1)->columnMode(), AbstractColumn::ColumnMode::Double);
0067 
0068     // values
0069     QCOMPARE(sheet.column(0)->valueAt(0), 10.0);
0070     QCOMPARE(sheet.column(1)->valueAt(0), 100.0);
0071     QCOMPARE(sheet.column(0)->valueAt(1), 20.0);
0072     QCOMPARE(sheet.column(1)->valueAt(1), 200.0);
0073 }
0074 
0075 /*!
0076    insert one column with integer and one column with big integer values into an empty spreadsheet.
0077    the first column has to be converted to integer column, the second to big integer.
0078 */
0079 void SpreadsheetTest::testCopyPasteColumnMode01() {
0080     Spreadsheet sheet(QStringLiteral("test"), false);
0081     sheet.setColumnCount(2);
0082     sheet.setRowCount(100);
0083 
0084     const QString str = QStringLiteral("10 ") + QString::number(std::numeric_limits<long long>::min()) + QStringLiteral("\n20 ")
0085         + QString::number(std::numeric_limits<long long>::max());
0086     QApplication::clipboard()->setText(str);
0087 
0088     SpreadsheetView view(&sheet, false);
0089     view.pasteIntoSelection();
0090 
0091     // spreadsheet size
0092     QCOMPARE(sheet.columnCount(), 2);
0093     QCOMPARE(sheet.rowCount(), 100);
0094 
0095     // column modes
0096     QCOMPARE(sheet.column(0)->columnMode(), AbstractColumn::ColumnMode::Integer);
0097     QCOMPARE(sheet.column(1)->columnMode(), AbstractColumn::ColumnMode::BigInt);
0098 
0099     // values
0100     QCOMPARE(sheet.column(0)->integerAt(0), 10);
0101     QCOMPARE(sheet.column(1)->bigIntAt(0), std::numeric_limits<long long>::min());
0102     QCOMPARE(sheet.column(0)->integerAt(1), 20);
0103     QCOMPARE(sheet.column(1)->bigIntAt(1), std::numeric_limits<long long>::max());
0104 }
0105 
0106 /*!
0107    insert one column with integer values and one column with float numbers into an empty spreadsheet.
0108    the first column has to be converted to integer column, the second to float.
0109 */
0110 void SpreadsheetTest::testCopyPasteColumnMode02() {
0111     QLocale::setDefault(QLocale::C); // . as decimal separator
0112     Spreadsheet sheet(QStringLiteral("test"), false);
0113     sheet.setColumnCount(2);
0114     sheet.setRowCount(100);
0115 
0116     const QString str = QStringLiteral("10 100.0\n20 200.0");
0117     QApplication::clipboard()->setText(str);
0118 
0119     SpreadsheetView view(&sheet, false);
0120     view.pasteIntoSelection();
0121 
0122     // spreadsheet size
0123     QCOMPARE(sheet.columnCount(), 2);
0124     QCOMPARE(sheet.rowCount(), 100);
0125 
0126     // column modes
0127     QCOMPARE(sheet.column(0)->columnMode(), AbstractColumn::ColumnMode::Integer);
0128     QCOMPARE(sheet.column(1)->columnMode(), AbstractColumn::ColumnMode::Double);
0129 
0130     // values
0131     QCOMPARE(sheet.column(0)->integerAt(0), 10);
0132     QCOMPARE(sheet.column(1)->valueAt(0), 100.0);
0133     QCOMPARE(sheet.column(0)->integerAt(1), 20);
0134     QCOMPARE(sheet.column(1)->valueAt(1), 200.0);
0135 }
0136 
0137 /*!
0138    Properly handle empty values in the tab separated data.
0139 */
0140 void SpreadsheetTest::testCopyPasteColumnMode03() {
0141     QLocale::setDefault(QLocale::C); // . as decimal separator
0142     Spreadsheet sheet(QStringLiteral("test"), false);
0143     sheet.setColumnCount(2);
0144     sheet.setRowCount(100);
0145 
0146     const QString str = QStringLiteral(
0147         "1000       1000        1000\n"
0148         "985        985     985\n"
0149         "970    -7.06562    970     970\n"
0150         "955    -5.93881    955     955\n"
0151         "940    -4.97594    940 -4.97594    940");
0152 
0153     QApplication::clipboard()->setText(str);
0154 
0155     SpreadsheetView view(&sheet, false);
0156     view.pasteIntoSelection();
0157 
0158     // spreadsheet size
0159     QCOMPARE(sheet.columnCount(), 5);
0160     QCOMPARE(sheet.rowCount(), 100);
0161 
0162     // column modes
0163     QCOMPARE(sheet.column(0)->columnMode(), AbstractColumn::ColumnMode::Integer);
0164     QCOMPARE(sheet.column(1)->columnMode(), AbstractColumn::ColumnMode::Double);
0165     QCOMPARE(sheet.column(2)->columnMode(), AbstractColumn::ColumnMode::Integer);
0166     QCOMPARE(sheet.column(3)->columnMode(), AbstractColumn::ColumnMode::Double);
0167     QCOMPARE(sheet.column(4)->columnMode(), AbstractColumn::ColumnMode::Integer);
0168 
0169     // values
0170     QCOMPARE(sheet.column(0)->integerAt(0), 1000);
0171     QCOMPARE((bool)std::isnan(sheet.column(1)->valueAt(0)), true);
0172     QCOMPARE(sheet.column(2)->integerAt(0), 1000);
0173     QCOMPARE((bool)std::isnan(sheet.column(3)->valueAt(0)), true);
0174     QCOMPARE(sheet.column(4)->integerAt(0), 1000);
0175 
0176     QCOMPARE(sheet.column(0)->integerAt(1), 985);
0177     QCOMPARE((bool)std::isnan(sheet.column(1)->valueAt(1)), true);
0178     QCOMPARE(sheet.column(2)->integerAt(1), 985);
0179     QCOMPARE((bool)std::isnan(sheet.column(3)->valueAt(1)), true);
0180     QCOMPARE(sheet.column(4)->integerAt(1), 985);
0181 
0182     QCOMPARE(sheet.column(0)->integerAt(2), 970);
0183     QCOMPARE(sheet.column(1)->valueAt(2), -7.06562);
0184     QCOMPARE(sheet.column(2)->integerAt(2), 970);
0185     QCOMPARE((bool)std::isnan(sheet.column(3)->valueAt(2)), true);
0186     QCOMPARE(sheet.column(4)->integerAt(2), 970);
0187 
0188     QCOMPARE(sheet.column(0)->integerAt(3), 955);
0189     QCOMPARE(sheet.column(1)->valueAt(3), -5.93881);
0190     QCOMPARE(sheet.column(2)->integerAt(3), 955);
0191     QCOMPARE((bool)std::isnan(sheet.column(3)->valueAt(3)), true);
0192     QCOMPARE(sheet.column(4)->integerAt(3), 955);
0193 
0194     QCOMPARE(sheet.column(0)->integerAt(4), 940);
0195     QCOMPARE(sheet.column(1)->valueAt(4), -4.97594);
0196     QCOMPARE(sheet.column(2)->integerAt(4), 940);
0197     QCOMPARE(sheet.column(1)->valueAt(4), -4.97594);
0198     QCOMPARE(sheet.column(4)->integerAt(4), 940);
0199 }
0200 
0201 /*!
0202     automatically detect the proper format for the datetime columns
0203  */
0204 void SpreadsheetTest::testCopyPasteColumnMode04() {
0205     QLocale::setDefault(QLocale::C); // . as decimal separator
0206     Spreadsheet sheet(QStringLiteral("test"), false);
0207     sheet.setColumnCount(2);
0208     sheet.setRowCount(100);
0209 
0210     const QString str = QStringLiteral(
0211         "2020-09-20 11:21:40:849    7.7\n"
0212         "2020-09-20 11:21:41:830    4.2");
0213 
0214     QApplication::clipboard()->setText(str);
0215 
0216     SpreadsheetView view(&sheet, false);
0217     view.pasteIntoSelection();
0218 
0219     // spreadsheet size
0220     QCOMPARE(sheet.columnCount(), 2);
0221     QCOMPARE(sheet.rowCount(), 100);
0222 
0223     // column modes
0224     QCOMPARE(sheet.column(0)->columnMode(), AbstractColumn::ColumnMode::DateTime);
0225     QCOMPARE(sheet.column(1)->columnMode(), AbstractColumn::ColumnMode::Double);
0226 
0227     // values
0228     auto* filter = static_cast<DateTime2StringFilter*>(sheet.column(0)->outputFilter());
0229     const QString& format = filter->format();
0230 
0231     QCOMPARE(sheet.column(0)->dateTimeAt(0).toString(format), QLatin1String("2020-09-20 11:21:40:849"));
0232     QCOMPARE(sheet.column(1)->valueAt(0), 7.7);
0233 
0234     QCOMPARE(sheet.column(0)->dateTimeAt(1).toString(format), QLatin1String("2020-09-20 11:21:41:830"));
0235     QCOMPARE(sheet.column(1)->valueAt(1), 4.2);
0236 }
0237 
0238 /*!
0239     automatically detect the proper format for the datetime columns, time part only
0240  */
0241 void SpreadsheetTest::testCopyPasteColumnMode05() {
0242     QLocale::setDefault(QLocale::C); // . as decimal separator
0243     Spreadsheet sheet(QStringLiteral("test"), false);
0244     sheet.setColumnCount(2);
0245     sheet.setRowCount(100);
0246 
0247     const QString str = QStringLiteral(
0248         "11:21:40   7.7\n"
0249         "11:21:41   4.2");
0250 
0251     QApplication::clipboard()->setText(str);
0252 
0253     SpreadsheetView view(&sheet, false);
0254     view.pasteIntoSelection();
0255 
0256     // spreadsheet size
0257     QCOMPARE(sheet.columnCount(), 2);
0258     QCOMPARE(sheet.rowCount(), 100);
0259 
0260     // column modes
0261     QCOMPARE(sheet.column(0)->columnMode(), AbstractColumn::ColumnMode::DateTime);
0262     QCOMPARE(sheet.column(1)->columnMode(), AbstractColumn::ColumnMode::Double);
0263 
0264     // values
0265     auto* filter = static_cast<DateTime2StringFilter*>(sheet.column(0)->outputFilter());
0266     const QString& format = filter->format();
0267 
0268     QCOMPARE(sheet.column(0)->dateTimeAt(0).toString(format), QLatin1String("11:21:40"));
0269     QCOMPARE(sheet.column(1)->valueAt(0), 7.7);
0270 
0271     QCOMPARE(sheet.column(0)->dateTimeAt(1).toString(format), QLatin1String("11:21:41"));
0272     QCOMPARE(sheet.column(1)->valueAt(1), 4.2);
0273 }
0274 
0275 /*!
0276     automatically detect the proper format for the datetime columns having the format "yyyy-MM-dd hh:mm:ss"
0277  */
0278 void SpreadsheetTest::testCopyPasteColumnMode06() {
0279     Spreadsheet sheet(QStringLiteral("test"), false);
0280     sheet.setColumnCount(2);
0281     sheet.setRowCount(100);
0282 
0283     const QString str = QStringLiteral(
0284         "2018-03-21 10:00:00 1\n"
0285         "2018-03-21 10:30:00 2");
0286 
0287     QApplication::clipboard()->setText(str);
0288 
0289     SpreadsheetView view(&sheet, false);
0290     view.pasteIntoSelection();
0291 
0292     // spreadsheet size
0293     QCOMPARE(sheet.columnCount(), 2);
0294     QCOMPARE(sheet.rowCount(), 100);
0295 
0296     // column modes
0297     QCOMPARE(sheet.column(0)->columnMode(), AbstractColumn::ColumnMode::DateTime);
0298     QCOMPARE(sheet.column(1)->columnMode(), AbstractColumn::ColumnMode::Integer);
0299 
0300     // values
0301     auto* filter = static_cast<DateTime2StringFilter*>(sheet.column(0)->outputFilter());
0302     const QString& format = filter->format();
0303 
0304     QCOMPARE(sheet.column(0)->dateTimeAt(0).toString(format), QLatin1String("2018-03-21 10:00:00"));
0305     QCOMPARE(sheet.column(1)->integerAt(0), 1);
0306 
0307     QCOMPARE(sheet.column(0)->dateTimeAt(1).toString(format), QLatin1String("2018-03-21 10:30:00"));
0308     QCOMPARE(sheet.column(1)->integerAt(1), 2);
0309 }
0310 
0311 //**********************************************************
0312 //********* Handling of spreadsheet size changes ***********
0313 //**********************************************************
0314 /*!
0315    insert irregular data, new columns should be added appropriately.
0316 */
0317 void SpreadsheetTest::testCopyPasteSizeChange00() {
0318     Project project;
0319     Spreadsheet* sheet = new Spreadsheet(QStringLiteral("test"), false);
0320     project.addChild(sheet);
0321     sheet->setColumnCount(2);
0322     sheet->setRowCount(100);
0323 
0324     const QString str = QStringLiteral(
0325         "0\n"
0326         "10 20\n"
0327         "11 21 31\n"
0328         "12 22 32 42\n"
0329         "13 23\n"
0330         "14");
0331     QApplication::clipboard()->setText(str);
0332 
0333     SpreadsheetView view(sheet, false);
0334     view.pasteIntoSelection();
0335 
0336     // spreadsheet size
0337     QCOMPARE(sheet->columnCount(), 4);
0338     QCOMPARE(sheet->rowCount(), 100);
0339 
0340     // column modes
0341     QCOMPARE(sheet->column(0)->columnMode(), AbstractColumn::ColumnMode::Integer);
0342     QCOMPARE(sheet->column(1)->columnMode(), AbstractColumn::ColumnMode::Integer);
0343     QCOMPARE(sheet->column(2)->columnMode(), AbstractColumn::ColumnMode::Integer);
0344     QCOMPARE(sheet->column(3)->columnMode(), AbstractColumn::ColumnMode::Integer);
0345 
0346     // values
0347     QCOMPARE(sheet->column(0)->integerAt(0), 0);
0348     QCOMPARE(sheet->column(1)->integerAt(0), 0);
0349     QCOMPARE(sheet->column(2)->integerAt(0), 0);
0350     QCOMPARE(sheet->column(3)->integerAt(0), 0);
0351 
0352     QCOMPARE(sheet->column(0)->integerAt(1), 10);
0353     QCOMPARE(sheet->column(1)->integerAt(1), 20);
0354     QCOMPARE(sheet->column(2)->integerAt(1), 0);
0355     QCOMPARE(sheet->column(3)->integerAt(1), 0);
0356 
0357     QCOMPARE(sheet->column(0)->integerAt(2), 11);
0358     QCOMPARE(sheet->column(1)->integerAt(2), 21);
0359     QCOMPARE(sheet->column(2)->integerAt(2), 31);
0360     QCOMPARE(sheet->column(3)->integerAt(2), 0);
0361 
0362     QCOMPARE(sheet->column(0)->integerAt(3), 12);
0363     QCOMPARE(sheet->column(1)->integerAt(3), 22);
0364     QCOMPARE(sheet->column(2)->integerAt(3), 32);
0365     QCOMPARE(sheet->column(3)->integerAt(3), 42);
0366 
0367     QCOMPARE(sheet->column(0)->integerAt(4), 13);
0368     QCOMPARE(sheet->column(1)->integerAt(4), 23);
0369     QCOMPARE(sheet->column(2)->integerAt(4), 0);
0370     QCOMPARE(sheet->column(3)->integerAt(4), 0);
0371 
0372     QCOMPARE(sheet->column(0)->integerAt(5), 14);
0373     QCOMPARE(sheet->column(1)->integerAt(5), 0);
0374     QCOMPARE(sheet->column(2)->integerAt(5), 0);
0375     QCOMPARE(sheet->column(3)->integerAt(5), 0);
0376 
0377     // undo the changes and check the results again
0378     project.undoStack()->undo();
0379 
0380     // spreadsheet size
0381     QCOMPARE(sheet->columnCount(), 2);
0382     QCOMPARE(sheet->rowCount(), 100);
0383 
0384     // column modes
0385     QCOMPARE(sheet->column(0)->columnMode(), AbstractColumn::ColumnMode::Double);
0386     QCOMPARE(sheet->column(1)->columnMode(), AbstractColumn::ColumnMode::Double);
0387 
0388     // values
0389     QCOMPARE((bool)std::isnan(sheet->column(0)->valueAt(0)), true);
0390     QCOMPARE((bool)std::isnan(sheet->column(1)->valueAt(0)), true);
0391 
0392     QCOMPARE((bool)std::isnan(sheet->column(0)->valueAt(1)), true);
0393     QCOMPARE((bool)std::isnan(sheet->column(1)->valueAt(1)), true);
0394 
0395     QCOMPARE((bool)std::isnan(sheet->column(0)->valueAt(2)), true);
0396     QCOMPARE((bool)std::isnan(sheet->column(1)->valueAt(2)), true);
0397 
0398     QCOMPARE((bool)std::isnan(sheet->column(0)->valueAt(3)), true);
0399     QCOMPARE((bool)std::isnan(sheet->column(1)->valueAt(3)), true);
0400 
0401     QCOMPARE((bool)std::isnan(sheet->column(0)->valueAt(4)), true);
0402     QCOMPARE((bool)std::isnan(sheet->column(1)->valueAt(4)), true);
0403 
0404     QCOMPARE((bool)std::isnan(sheet->column(0)->valueAt(5)), true);
0405     QCOMPARE((bool)std::isnan(sheet->column(1)->valueAt(5)), true);
0406 }
0407 
0408 void SpreadsheetTest::testCopyPasteUtf8() {
0409     Spreadsheet sheet(QStringLiteral("test"), false);
0410     sheet.setColumnCount(2);
0411     sheet.setRowCount(100);
0412 
0413     const QString str = QString::fromUtf8("тест1 1\nтест2 2");
0414 
0415     QApplication::clipboard()->setText(str);
0416 
0417     SpreadsheetView view(&sheet, false);
0418     view.pasteIntoSelection();
0419 
0420     // data types
0421     QCOMPARE(sheet.column(0)->columnMode(), AbstractColumn::ColumnMode::Text);
0422     QCOMPARE(sheet.column(1)->columnMode(), AbstractColumn::ColumnMode::Integer);
0423 
0424     // values
0425     QCOMPARE(sheet.column(0)->textAt(0), QString::fromUtf8("тест1"));
0426     QCOMPARE(sheet.column(1)->integerAt(0), 1);
0427 
0428     QCOMPARE(sheet.column(0)->textAt(1), QString::fromUtf8("тест2"));
0429     QCOMPARE(sheet.column(1)->integerAt(1), 2);
0430 }
0431 /*!
0432    insert the data at the edge of the spreadsheet and paste the data.
0433    the spreadsheet has to be extended accordingly
0434 */
0435 void SpreadsheetTest::testCopyPasteSizeChange01() {
0436     QLocale::setDefault(QLocale::C); // . as decimal separator
0437     Spreadsheet sheet(QStringLiteral("test"), false);
0438     sheet.setColumnCount(2);
0439     sheet.setRowCount(100);
0440 
0441     const QString str = QStringLiteral(
0442         "1.1 2.2\n"
0443         "3.3 4.4");
0444     QApplication::clipboard()->setText(str);
0445 
0446     SpreadsheetView view(&sheet, false);
0447     view.goToCell(1, 1); // havigate to the edge of the spreadsheet
0448     view.pasteIntoSelection();
0449 
0450     // spreadsheet size
0451     QCOMPARE(sheet.columnCount(), 3);
0452     QCOMPARE(sheet.rowCount(), 100);
0453 
0454     // column modes
0455     QCOMPARE(sheet.column(0)->columnMode(), AbstractColumn::ColumnMode::Double);
0456     QCOMPARE(sheet.column(1)->columnMode(), AbstractColumn::ColumnMode::Double);
0457     QCOMPARE(sheet.column(2)->columnMode(), AbstractColumn::ColumnMode::Double);
0458 
0459     // values
0460     QCOMPARE((bool)std::isnan(sheet.column(0)->valueAt(0)), true);
0461     QCOMPARE((bool)std::isnan(sheet.column(1)->valueAt(0)), true);
0462     QCOMPARE((bool)std::isnan(sheet.column(2)->valueAt(0)), true);
0463 
0464     QCOMPARE((bool)std::isnan(sheet.column(0)->valueAt(1)), true);
0465     QCOMPARE(sheet.column(1)->valueAt(1), 1.1);
0466     QCOMPARE(sheet.column(2)->valueAt(1), 2.2);
0467 
0468     QCOMPARE((bool)std::isnan(sheet.column(0)->valueAt(2)), true);
0469     QCOMPARE(sheet.column(1)->valueAt(2), 3.3);
0470     QCOMPARE(sheet.column(2)->valueAt(2), 4.4);
0471 }
0472 
0473 // **********************************************************
0474 // *********************** sorting  *************************
0475 // **********************************************************
0476 // single column
0477 
0478 /*
0479  * check sorting single column of double values with NaN ascending
0480  */
0481 void SpreadsheetTest::testSortSingleNumeric1() {
0482     const QVector<double> xData{0.5, -0.2, GSL_NAN, 2.0, -1.0};
0483 
0484     Spreadsheet sheet(QStringLiteral("test"), false);
0485     sheet.setColumnCount(1);
0486     sheet.setRowCount(7);
0487     auto* col = sheet.column(0);
0488     col->replaceValues(0, xData);
0489 
0490     // sort
0491     sheet.sortColumns(nullptr, {col}, true);
0492 
0493     // values
0494     QCOMPARE(col->valueAt(0), -1.0);
0495     QCOMPARE(col->valueAt(1), -0.2);
0496     QCOMPARE(col->valueAt(2), 0.5);
0497     QCOMPARE(col->valueAt(3), 2.0);
0498 }
0499 
0500 /*
0501  * check sorting single column of double values with NaN descending
0502  */
0503 void SpreadsheetTest::testSortSingleNumeric2() {
0504     const QVector<double> xData{0.5, -0.2, GSL_NAN, 2.0, -1.0};
0505 
0506     Spreadsheet sheet(QStringLiteral("test"), false);
0507     sheet.setColumnCount(1);
0508     sheet.setRowCount(7);
0509     auto* col = sheet.column(0);
0510     col->replaceValues(0, xData);
0511 
0512     // sort
0513     sheet.sortColumns(nullptr, {col}, false);
0514 
0515     // values
0516     QCOMPARE(col->valueAt(0), 2.0);
0517     QCOMPARE(col->valueAt(1), 0.5);
0518     QCOMPARE(col->valueAt(2), -0.2);
0519     QCOMPARE(col->valueAt(3), -1.0);
0520 }
0521 
0522 /*
0523  * check sorting single column of integer values with empty entries ascending
0524  */
0525 void SpreadsheetTest::testSortSingleInteger1() {
0526     const QVector<int> xData1{4, 5, 2};
0527     const QVector<int> xData2{3, 6, -1};
0528 
0529     Spreadsheet sheet(QStringLiteral("test"), false);
0530     sheet.setColumnCount(1);
0531     sheet.setRowCount(7);
0532     auto* col = sheet.column(0);
0533     col->setColumnMode(AbstractColumn::ColumnMode::Integer);
0534     col->replaceInteger(0, xData1);
0535     col->replaceInteger(4, xData2);
0536 
0537     // sort
0538     sheet.sortColumns(nullptr, {col}, true);
0539 
0540     // values
0541     QCOMPARE(col->integerAt(0), -1);
0542     QCOMPARE(col->integerAt(1), 0);
0543     QCOMPARE(col->integerAt(2), 2);
0544     QCOMPARE(col->integerAt(3), 3);
0545     QCOMPARE(col->integerAt(4), 4);
0546     QCOMPARE(col->integerAt(5), 5);
0547     QCOMPARE(col->integerAt(6), 6);
0548 }
0549 
0550 /*
0551  * check sorting single column of integer values with empty entries ascending
0552  */
0553 void SpreadsheetTest::testSortSingleInteger2() {
0554     const QVector<int> xData1{4, 5, 2};
0555     const QVector<int> xData2{3, 6, -1};
0556 
0557     Spreadsheet sheet(QStringLiteral("test"), false);
0558     sheet.setColumnCount(1);
0559     sheet.setRowCount(7);
0560     auto* col = sheet.column(0);
0561     col->setColumnMode(AbstractColumn::ColumnMode::Integer);
0562     col->replaceInteger(0, xData1);
0563     col->replaceInteger(4, xData2);
0564 
0565     // sort
0566     sheet.sortColumns(nullptr, {col}, false);
0567 
0568     // values
0569     QCOMPARE(col->integerAt(6), -1);
0570     QCOMPARE(col->integerAt(5), 0);
0571     QCOMPARE(col->integerAt(4), 2);
0572     QCOMPARE(col->integerAt(3), 3);
0573     QCOMPARE(col->integerAt(2), 4);
0574     QCOMPARE(col->integerAt(1), 5);
0575     QCOMPARE(col->integerAt(0), 6);
0576 }
0577 
0578 /*
0579  * check sorting single column of big int values with empty entries ascending
0580  */
0581 void SpreadsheetTest::testSortSingleBigInt1() {
0582     const QVector<qint64> xData1{40000000000, 50000000000, 20000000000};
0583     const QVector<qint64> xData2{30000000000, 60000000000, -10000000000};
0584 
0585     Spreadsheet sheet(QStringLiteral("test"), false);
0586     sheet.setColumnCount(1);
0587     sheet.setRowCount(7);
0588     auto* col = sheet.column(0);
0589     col->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0590     col->replaceBigInt(0, xData1);
0591     col->replaceBigInt(4, xData2);
0592 
0593     // sort
0594     sheet.sortColumns(nullptr, {col}, true);
0595 
0596     // values
0597     QCOMPARE(col->bigIntAt(0), -10000000000ll);
0598     QCOMPARE(col->bigIntAt(1), 0);
0599     QCOMPARE(col->bigIntAt(2), 20000000000ll);
0600     QCOMPARE(col->bigIntAt(3), 30000000000ll);
0601     QCOMPARE(col->bigIntAt(4), 40000000000ll);
0602     QCOMPARE(col->bigIntAt(5), 50000000000ll);
0603     QCOMPARE(col->bigIntAt(6), 60000000000ll);
0604 }
0605 
0606 /*
0607  * check sorting single column of big int values with empty entries descending
0608  */
0609 void SpreadsheetTest::testSortSingleBigInt2() {
0610     const QVector<qint64> xData1{40000000000, 50000000000, 20000000000};
0611     const QVector<qint64> xData2{30000000000, 60000000000, -10000000000};
0612 
0613     Spreadsheet sheet(QStringLiteral("test"), false);
0614     sheet.setColumnCount(1);
0615     sheet.setRowCount(7);
0616     auto* col = sheet.column(0);
0617     col->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0618     col->replaceBigInt(0, xData1);
0619     col->replaceBigInt(4, xData2);
0620 
0621     // sort
0622     sheet.sortColumns(nullptr, {col}, false);
0623 
0624     // values
0625     QCOMPARE(col->bigIntAt(6), -10000000000ll);
0626     QCOMPARE(col->bigIntAt(5), 0);
0627     QCOMPARE(col->bigIntAt(4), 20000000000ll);
0628     QCOMPARE(col->bigIntAt(3), 30000000000ll);
0629     QCOMPARE(col->bigIntAt(2), 40000000000ll);
0630     QCOMPARE(col->bigIntAt(1), 50000000000ll);
0631     QCOMPARE(col->bigIntAt(0), 60000000000ll);
0632 }
0633 
0634 /*
0635  * check sorting single column of text with empty entries ascending
0636  */
0637 void SpreadsheetTest::testSortSingleText1() {
0638     const QVector<QString> xData{QStringLiteral("ben"),
0639                                  QStringLiteral("amy"),
0640                                  QStringLiteral("eddy"),
0641                                  QString(),
0642                                  QStringLiteral("carl"),
0643                                  QStringLiteral("dan")};
0644 
0645     Spreadsheet sheet(QStringLiteral("test"), false);
0646     sheet.setColumnCount(1);
0647     sheet.setRowCount(8);
0648     auto* col = sheet.column(0);
0649     col->setColumnMode(AbstractColumn::ColumnMode::Text);
0650     col->replaceTexts(0, xData);
0651 
0652     // sort
0653     sheet.sortColumns(nullptr, {col}, true);
0654 
0655     // values
0656     QCOMPARE(col->textAt(0), QLatin1String("amy"));
0657     QCOMPARE(col->textAt(1), QLatin1String("ben"));
0658     QCOMPARE(col->textAt(2), QLatin1String("carl"));
0659     QCOMPARE(col->textAt(3), QLatin1String("dan"));
0660     QCOMPARE(col->textAt(4), QLatin1String("eddy"));
0661     QCOMPARE(col->textAt(5), QString());
0662     QCOMPARE(col->textAt(6), QString());
0663 }
0664 
0665 /*
0666  * check sorting single column of text with empty entries descending
0667  */
0668 void SpreadsheetTest::testSortSingleText2() {
0669     const QVector<QString> xData =
0670         {QStringLiteral("ben"), QStringLiteral("amy"), QStringLiteral("eddy"), QString(), QStringLiteral("carl"), QStringLiteral("dan")};
0671 
0672     Spreadsheet sheet(QStringLiteral("test"), false);
0673     sheet.setColumnCount(1);
0674     sheet.setRowCount(8);
0675     auto* col = sheet.column(0);
0676     col->setColumnMode(AbstractColumn::ColumnMode::Text);
0677     col->replaceTexts(0, xData);
0678 
0679     // sort
0680     sheet.sortColumns(nullptr, {col}, false);
0681 
0682     // values
0683     QCOMPARE(col->textAt(0), QLatin1String("eddy"));
0684     QCOMPARE(col->textAt(1), QLatin1String("dan"));
0685     QCOMPARE(col->textAt(2), QLatin1String("carl"));
0686     QCOMPARE(col->textAt(3), QLatin1String("ben"));
0687     QCOMPARE(col->textAt(4), QLatin1String("amy"));
0688     QCOMPARE(col->textAt(5), QString());
0689     QCOMPARE(col->textAt(6), QString());
0690 }
0691 
0692 /*
0693  * check sorting single column of datetimes with invalid entries ascending
0694  */
0695 void SpreadsheetTest::testSortSingleDateTime1() {
0696     const QVector<QDateTime> xData{
0697         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)),
0698         QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)),
0699         QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)),
0700         QDateTime(QDate(2019, 02, 29), QTime(12, 12, 12)), // invalid
0701         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)),
0702         QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)),
0703     };
0704 
0705     Spreadsheet sheet(QStringLiteral("test"), false);
0706     sheet.setColumnCount(1);
0707     sheet.setRowCount(8);
0708     auto* col{sheet.column(0)};
0709     col->setColumnMode(AbstractColumn::ColumnMode::DateTime);
0710     col->replaceDateTimes(0, xData);
0711 
0712     // sort
0713     sheet.sortColumns(nullptr, {col}, true);
0714 
0715     // values
0716     QCOMPARE(col->dateTimeAt(0), QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)));
0717     QCOMPARE(col->dateTimeAt(1), QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)));
0718     QCOMPARE(col->dateTimeAt(2), QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)));
0719     QCOMPARE(col->dateTimeAt(3), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)));
0720     QCOMPARE(col->dateTimeAt(4), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)));
0721 }
0722 
0723 /*
0724  * check sorting single column of datetimes with invalid entries descending
0725  */
0726 void SpreadsheetTest::testSortSingleDateTime2() {
0727     const QVector<QDateTime> xData{
0728         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)),
0729         QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)),
0730         QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)),
0731         QDateTime(QDate(2019, 02, 29), QTime(12, 12, 12)), // invalid
0732         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)),
0733         QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)),
0734     };
0735 
0736     Spreadsheet sheet(QStringLiteral("test"), false);
0737     sheet.setColumnCount(1);
0738     sheet.setRowCount(8);
0739     auto* col = sheet.column(0);
0740     col->setColumnMode(AbstractColumn::ColumnMode::DateTime);
0741     col->replaceDateTimes(0, xData);
0742 
0743     // sort
0744     sheet.sortColumns(nullptr, {col}, false);
0745 
0746     // values
0747     QCOMPARE(col->dateTimeAt(4), QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)));
0748     QCOMPARE(col->dateTimeAt(3), QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)));
0749     QCOMPARE(col->dateTimeAt(2), QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)));
0750     QCOMPARE(col->dateTimeAt(1), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)));
0751     QCOMPARE(col->dateTimeAt(0), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)));
0752 }
0753 
0754 // multiple column
0755 /*
0756  * check sorting double values with NaN ascending as leading column
0757  */
0758 void SpreadsheetTest::testSortNumeric1() {
0759     const QVector<double> xData{0.5, -0.2, GSL_NAN, 2.0, -1.0};
0760     const QVector<int> yData{1, 2, 3, 4, 5, 6};
0761 
0762     Spreadsheet sheet(QStringLiteral("test"), false);
0763     sheet.setColumnCount(2);
0764     sheet.setRowCount(10);
0765     auto* col0{sheet.column(0)};
0766     auto* col1{sheet.column(1)};
0767     col0->replaceValues(0, xData);
0768     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
0769     col1->replaceInteger(0, yData);
0770 
0771     // sort
0772     sheet.sortColumns(col0, {col0, col1}, true);
0773 
0774     // values
0775     QCOMPARE(col0->valueAt(0), -1.0);
0776     QCOMPARE(col0->valueAt(1), -0.2);
0777     QCOMPARE(col0->valueAt(2), 0.5);
0778     QCOMPARE(col0->valueAt(3), 2.0);
0779     // QCOMPARE(col0->valueAt(4), GSL_NAN);
0780     QCOMPARE(col1->integerAt(0), 5);
0781     QCOMPARE(col1->integerAt(1), 2);
0782     QCOMPARE(col1->integerAt(2), 1);
0783     QCOMPARE(col1->integerAt(3), 4);
0784     QCOMPARE(col1->integerAt(4), 3);
0785     QCOMPARE(col1->integerAt(5), 6);
0786 }
0787 
0788 /*
0789  * check sorting double values with NaN descending as leading column
0790  */
0791 void SpreadsheetTest::testSortNumeric2() {
0792     const QVector<double> xData{0.5, -0.2, GSL_NAN, 2.0, -1.0};
0793     const QVector<int> yData{1, 2, 3, 4, 5, 6, 7};
0794 
0795     Spreadsheet sheet(QStringLiteral("test"), false);
0796     sheet.setColumnCount(2);
0797     sheet.setRowCount(10);
0798     auto* col0{sheet.column(0)};
0799     auto* col1{sheet.column(1)};
0800     col0->replaceValues(0, xData);
0801     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
0802     col1->replaceInteger(0, yData);
0803 
0804     // sort
0805     sheet.sortColumns(col0, {col0, col1}, false);
0806 
0807     // values
0808     QCOMPARE(col0->valueAt(0), 2.0);
0809     QCOMPARE(col0->valueAt(1), 0.5);
0810     QCOMPARE(col0->valueAt(2), -0.2);
0811     QCOMPARE(col0->valueAt(3), -1.0);
0812     QCOMPARE(col1->integerAt(0), 4);
0813     QCOMPARE(col1->integerAt(1), 1);
0814     QCOMPARE(col1->integerAt(2), 2);
0815     QCOMPARE(col1->integerAt(3), 5);
0816     QCOMPARE(col1->integerAt(4), 3);
0817     QCOMPARE(col1->integerAt(5), 6);
0818     QCOMPARE(col1->integerAt(6), 7);
0819 }
0820 
0821 /*
0822  * check sorting integer values with empty entries ascending as leading column
0823  */
0824 void SpreadsheetTest::testSortInteger1() {
0825     const QVector<int> xData1{4, 5, 2};
0826     const QVector<int> xData2{3, 6, -1};
0827     const QVector<int> yData{1, 2, 3, 4, 5, 6, 7};
0828 
0829     Spreadsheet sheet(QStringLiteral("test"), false);
0830     sheet.setColumnCount(2);
0831     sheet.setRowCount(7);
0832     auto* col0{sheet.column(0)};
0833     auto* col1{sheet.column(1)};
0834     col0->setColumnMode(AbstractColumn::ColumnMode::Integer);
0835     col0->replaceInteger(0, xData1);
0836     col0->replaceInteger(4, xData2);
0837     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
0838     col1->replaceInteger(0, yData);
0839 
0840     // sort
0841     sheet.sortColumns(col0, {col0, col1}, true);
0842 
0843     // values
0844     QCOMPARE(col0->integerAt(0), -1);
0845     QCOMPARE(col0->integerAt(1), 0);
0846     QCOMPARE(col0->integerAt(2), 2);
0847     QCOMPARE(col0->integerAt(3), 3);
0848     QCOMPARE(col0->integerAt(4), 4);
0849     QCOMPARE(col0->integerAt(5), 5);
0850     QCOMPARE(col0->integerAt(6), 6);
0851     QCOMPARE(col1->integerAt(0), 7);
0852     QCOMPARE(col1->integerAt(1), 4);
0853     QCOMPARE(col1->integerAt(2), 3);
0854     QCOMPARE(col1->integerAt(3), 5);
0855     QCOMPARE(col1->integerAt(4), 1);
0856     QCOMPARE(col1->integerAt(5), 2);
0857     QCOMPARE(col1->integerAt(6), 6);
0858 }
0859 
0860 /*
0861  * check sorting integer values with empty entries descending as leading column
0862  */
0863 void SpreadsheetTest::testSortInteger2() {
0864     const QVector<int> xData1{4, 5, 2};
0865     const QVector<int> xData2{3, 6, -1};
0866     const QVector<int> yData{1, 2, 3, 4, 5, 6, 7};
0867 
0868     Spreadsheet sheet(QStringLiteral("test"), false);
0869     sheet.setColumnCount(2);
0870     sheet.setRowCount(7);
0871     auto* col0{sheet.column(0)};
0872     auto* col1{sheet.column(1)};
0873     col0->setColumnMode(AbstractColumn::ColumnMode::Integer);
0874     col0->replaceInteger(0, xData1);
0875     col0->replaceInteger(4, xData2);
0876     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
0877     col1->replaceInteger(0, yData);
0878 
0879     // sort
0880     sheet.sortColumns(col0, {col0, col1}, false);
0881 
0882     // values
0883     QCOMPARE(col0->integerAt(6), -1);
0884     QCOMPARE(col0->integerAt(5), 0);
0885     QCOMPARE(col0->integerAt(4), 2);
0886     QCOMPARE(col0->integerAt(3), 3);
0887     QCOMPARE(col0->integerAt(2), 4);
0888     QCOMPARE(col0->integerAt(1), 5);
0889     QCOMPARE(col0->integerAt(0), 6);
0890     QCOMPARE(col1->integerAt(6), 7);
0891     QCOMPARE(col1->integerAt(5), 4);
0892     QCOMPARE(col1->integerAt(4), 3);
0893     QCOMPARE(col1->integerAt(3), 5);
0894     QCOMPARE(col1->integerAt(2), 1);
0895     QCOMPARE(col1->integerAt(1), 2);
0896     QCOMPARE(col1->integerAt(0), 6);
0897 }
0898 
0899 /*
0900  * check sorting big int values with empty entries ascending as leading column
0901  */
0902 void SpreadsheetTest::testSortBigInt1() {
0903     Spreadsheet sheet(QStringLiteral("test"), false);
0904     sheet.setColumnCount(2);
0905     sheet.setRowCount(7);
0906 
0907     QVector<qint64> xData1{40000000000, 50000000000, 20000000000};
0908     QVector<qint64> xData2{30000000000, 60000000000, -10000000000};
0909     QVector<qint64> yData{1, 2, 3, 4, 5, 6, 7};
0910 
0911     auto* col0{sheet.column(0)};
0912     auto* col1{sheet.column(1)};
0913     col0->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0914     col0->replaceBigInt(0, xData1);
0915     col0->replaceBigInt(4, xData2);
0916     col1->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0917     col1->replaceBigInt(0, yData);
0918 
0919     // sort
0920     sheet.sortColumns(col0, {col0, col1}, true);
0921 
0922     // values
0923     QCOMPARE(col0->bigIntAt(0), -10000000000ll);
0924     QCOMPARE(col0->bigIntAt(1), 0);
0925     QCOMPARE(col0->bigIntAt(2), 20000000000ll);
0926     QCOMPARE(col0->bigIntAt(3), 30000000000ll);
0927     QCOMPARE(col0->bigIntAt(4), 40000000000ll);
0928     QCOMPARE(col0->bigIntAt(5), 50000000000ll);
0929     QCOMPARE(col0->bigIntAt(6), 60000000000ll);
0930     QCOMPARE(col1->bigIntAt(0), 7);
0931     QCOMPARE(col1->bigIntAt(1), 4);
0932     QCOMPARE(col1->bigIntAt(2), 3);
0933     QCOMPARE(col1->bigIntAt(3), 5);
0934     QCOMPARE(col1->bigIntAt(4), 1);
0935     QCOMPARE(col1->bigIntAt(5), 2);
0936     QCOMPARE(col1->bigIntAt(6), 6);
0937 }
0938 
0939 /*
0940  * check sorting big int values with empty entries descending as leading column
0941  */
0942 void SpreadsheetTest::testSortBigInt2() {
0943     Spreadsheet sheet(QStringLiteral("test"), false);
0944     sheet.setColumnCount(2);
0945     sheet.setRowCount(7);
0946 
0947     QVector<qint64> xData1{40000000000, 50000000000, 20000000000};
0948     QVector<qint64> xData2{30000000000, 60000000000, -10000000000};
0949     QVector<qint64> yData{1, 2, 3, 4, 5, 6, 7};
0950 
0951     auto* col0{sheet.column(0)};
0952     auto* col1{sheet.column(1)};
0953     col0->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0954     col0->replaceBigInt(0, xData1);
0955     col0->replaceBigInt(4, xData2);
0956     col1->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0957     col1->replaceBigInt(0, yData);
0958 
0959     // sort
0960     sheet.sortColumns(col0, {col0, col1}, false);
0961 
0962     // values
0963     QCOMPARE(col0->bigIntAt(6), -10000000000ll);
0964     QCOMPARE(col0->bigIntAt(5), 0);
0965     QCOMPARE(col0->bigIntAt(4), 20000000000ll);
0966     QCOMPARE(col0->bigIntAt(3), 30000000000ll);
0967     QCOMPARE(col0->bigIntAt(2), 40000000000ll);
0968     QCOMPARE(col0->bigIntAt(1), 50000000000ll);
0969     QCOMPARE(col0->bigIntAt(0), 60000000000ll);
0970     QCOMPARE(col1->bigIntAt(6), 7);
0971     QCOMPARE(col1->bigIntAt(5), 4);
0972     QCOMPARE(col1->bigIntAt(4), 3);
0973     QCOMPARE(col1->bigIntAt(3), 5);
0974     QCOMPARE(col1->bigIntAt(2), 1);
0975     QCOMPARE(col1->bigIntAt(1), 2);
0976     QCOMPARE(col1->bigIntAt(0), 6);
0977 }
0978 
0979 /*
0980  * check sorting text with empty entries ascending as leading column
0981  */
0982 void SpreadsheetTest::testSortText1() {
0983     Spreadsheet sheet(QStringLiteral("test"), false);
0984     sheet.setColumnCount(2);
0985     sheet.setRowCount(8);
0986 
0987     QVector<QString> xData{QStringLiteral("ben"), QStringLiteral("amy"), QStringLiteral("eddy"), QString(), QStringLiteral("carl"), QStringLiteral("dan")};
0988     QVector<int> yData{1, 2, 3, 4, 5, 6, 7};
0989 
0990     auto* col0{sheet.column(0)};
0991     auto* col1{sheet.column(1)};
0992     col0->setColumnMode(AbstractColumn::ColumnMode::Text);
0993     col0->replaceTexts(0, xData);
0994     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
0995     col1->replaceInteger(0, yData);
0996 
0997     // sort
0998     sheet.sortColumns(col0, {col0, col1}, true);
0999 
1000     // values
1001     QCOMPARE(col0->textAt(0), QLatin1String("amy"));
1002     QCOMPARE(col0->textAt(1), QLatin1String("ben"));
1003     QCOMPARE(col0->textAt(2), QLatin1String("carl"));
1004     QCOMPARE(col0->textAt(3), QLatin1String("dan"));
1005     QCOMPARE(col0->textAt(4), QLatin1String("eddy"));
1006     QCOMPARE(col0->textAt(5), QString());
1007     QCOMPARE(col0->textAt(6), QString());
1008     QCOMPARE(col1->integerAt(0), 2);
1009     QCOMPARE(col1->integerAt(1), 1);
1010     QCOMPARE(col1->integerAt(2), 5);
1011     QCOMPARE(col1->integerAt(3), 6);
1012     QCOMPARE(col1->integerAt(4), 3);
1013     QCOMPARE(col1->integerAt(5), 4);
1014     QCOMPARE(col1->integerAt(6), 7);
1015 }
1016 
1017 /*
1018  * check sorting text with empty entries descending as leading column
1019  */
1020 void SpreadsheetTest::testSortText2() {
1021     Spreadsheet sheet(QStringLiteral("test"), false);
1022     sheet.setColumnCount(2);
1023     sheet.setRowCount(8);
1024 
1025     QVector<QString> xData{QStringLiteral("ben"), QStringLiteral("amy"), QStringLiteral("eddy"), QString(), QStringLiteral("carl"), QStringLiteral("dan")};
1026     QVector<int> yData{1, 2, 3, 4, 5, 6, 7};
1027 
1028     auto* col0{sheet.column(0)};
1029     auto* col1{sheet.column(1)};
1030     col0->setColumnMode(AbstractColumn::ColumnMode::Text);
1031     col0->replaceTexts(0, xData);
1032     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
1033     col1->replaceInteger(0, yData);
1034 
1035     // sort
1036     sheet.sortColumns(col0, {col0, col1}, false);
1037 
1038     // values
1039     QCOMPARE(col0->textAt(4), QLatin1String("amy"));
1040     QCOMPARE(col0->textAt(3), QLatin1String("ben"));
1041     QCOMPARE(col0->textAt(2), QLatin1String("carl"));
1042     QCOMPARE(col0->textAt(1), QLatin1String("dan"));
1043     QCOMPARE(col0->textAt(0), QLatin1String("eddy"));
1044     QCOMPARE(col0->textAt(5), QString());
1045     QCOMPARE(col0->textAt(6), QString());
1046     QCOMPARE(col1->integerAt(4), 2);
1047     QCOMPARE(col1->integerAt(3), 1);
1048     QCOMPARE(col1->integerAt(2), 5);
1049     QCOMPARE(col1->integerAt(1), 6);
1050     QCOMPARE(col1->integerAt(0), 3);
1051     QCOMPARE(col1->integerAt(5), 4);
1052     QCOMPARE(col1->integerAt(6), 7);
1053 }
1054 
1055 /*
1056  * check sorting datetimes with invalid entries ascending as leading column
1057  */
1058 void SpreadsheetTest::testSortDateTime1() {
1059     Spreadsheet sheet(QStringLiteral("test"), false);
1060     sheet.setColumnCount(2);
1061     sheet.setRowCount(8);
1062 
1063     QVector<QDateTime> xData{
1064         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)),
1065         QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)),
1066         QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)),
1067         QDateTime(QDate(2019, 02, 29), QTime(12, 12, 12)), // invalid
1068         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)),
1069         QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)),
1070     };
1071     QVector<int> yData = {1, 2, 3, 4, 5, 6, 7};
1072 
1073     auto* col0{sheet.column(0)};
1074     auto* col1{sheet.column(1)};
1075     col0->setColumnMode(AbstractColumn::ColumnMode::DateTime);
1076     col0->replaceDateTimes(0, xData);
1077     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
1078     col1->replaceInteger(0, yData);
1079 
1080     // sort
1081     sheet.sortColumns(col0, {col0, col1}, true);
1082 
1083     // values
1084     QCOMPARE(col0->dateTimeAt(0), QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)));
1085     QCOMPARE(col0->dateTimeAt(1), QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)));
1086     QCOMPARE(col0->dateTimeAt(2), QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)));
1087     QCOMPARE(col0->dateTimeAt(3), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)));
1088     QCOMPARE(col0->dateTimeAt(4), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)));
1089     QCOMPARE(col1->integerAt(0), 3);
1090     QCOMPARE(col1->integerAt(1), 2);
1091     QCOMPARE(col1->integerAt(2), 6);
1092     QCOMPARE(col1->integerAt(3), 1);
1093     QCOMPARE(col1->integerAt(4), 5);
1094     QCOMPARE(col1->integerAt(5), 4);
1095     QCOMPARE(col1->integerAt(6), 7);
1096 }
1097 
1098 /*
1099  * check sorting datetimes with invalid entries descending as leading column
1100  */
1101 void SpreadsheetTest::testSortDateTime2() {
1102     const QVector<QDateTime> xData{
1103         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)),
1104         QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)),
1105         QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)),
1106         QDateTime(QDate(2019, 02, 29), QTime(12, 12, 12)), // invalid
1107         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)),
1108         QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)),
1109     };
1110     const QVector<int> yData = {1, 2, 3, 4, 5, 6, 7};
1111 
1112     Spreadsheet sheet(QStringLiteral("test"), false);
1113     sheet.setColumnCount(2);
1114     sheet.setRowCount(8);
1115     auto* col0{sheet.column(0)};
1116     auto* col1{sheet.column(1)};
1117     col0->setColumnMode(AbstractColumn::ColumnMode::DateTime);
1118     col0->replaceDateTimes(0, xData);
1119     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
1120     col1->replaceInteger(0, yData);
1121 
1122     // sort
1123     sheet.sortColumns(col0, {col0, col1}, false);
1124 
1125     // values
1126     QCOMPARE(col0->dateTimeAt(4), QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)));
1127     QCOMPARE(col0->dateTimeAt(3), QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)));
1128     QCOMPARE(col0->dateTimeAt(2), QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)));
1129     QCOMPARE(col0->dateTimeAt(1), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)));
1130     QCOMPARE(col0->dateTimeAt(0), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)));
1131     QCOMPARE(col1->integerAt(4), 3);
1132     QCOMPARE(col1->integerAt(3), 2);
1133     QCOMPARE(col1->integerAt(2), 6);
1134     QCOMPARE(col1->integerAt(1), 1);
1135     QCOMPARE(col1->integerAt(0), 5);
1136     QCOMPARE(col1->integerAt(5), 4);
1137     QCOMPARE(col1->integerAt(6), 7);
1138 }
1139 
1140 // performance
1141 
1142 /*
1143  * check performance of sorting double values in single column
1144  */
1145 void SpreadsheetTest::testSortPerformanceNumeric1() {
1146     Spreadsheet sheet(QStringLiteral("test"), false);
1147     sheet.setColumnCount(1);
1148     sheet.setRowCount(10000);
1149 
1150     QVector<double> xData;
1151     WARN("CREATE DATA")
1152     for (int i = 0; i < sheet.rowCount(); i++)
1153 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
1154         xData << QRandomGenerator::global()->generateDouble();
1155 #else
1156         xData << (double)(qrand()) / RAND_MAX;
1157 #endif
1158 
1159     auto* col = sheet.column(0);
1160     col->replaceValues(0, xData);
1161 
1162     // sort
1163     QBENCHMARK { sheet.sortColumns(nullptr, {col}, true); }
1164 }
1165 
1166 /*
1167  * check performance of sorting double values with two columns
1168  */
1169 void SpreadsheetTest::testSortPerformanceNumeric2() {
1170     Spreadsheet sheet(QStringLiteral("test"), false);
1171     sheet.setColumnCount(2);
1172     sheet.setRowCount(10000);
1173 
1174     QVector<double> xData;
1175     QVector<int> yData;
1176     WARN("CREATE DATA")
1177     for (int i = 0; i < sheet.rowCount(); i++) {
1178 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
1179         xData << QRandomGenerator::global()->generateDouble();
1180 #else
1181         xData << (double)(qrand()) / RAND_MAX;
1182 #endif
1183         yData << i + 1;
1184     }
1185 
1186     auto* col0{sheet.column(0)};
1187     auto* col1{sheet.column(1)};
1188     col0->replaceValues(0, xData);
1189     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
1190     col1->replaceInteger(0, yData);
1191 
1192     // sort
1193     QBENCHMARK { sheet.sortColumns(col0, {col0, col1}, true); }
1194 }
1195 
1196 // **********************************************************
1197 // ********************* drop/mask  *************************
1198 // **********************************************************
1199 void SpreadsheetTest::testRemoveRowsWithMissingValues() {
1200     // prepare the spreadsheet
1201     Spreadsheet sheet(QStringLiteral("test"), false);
1202     sheet.setColumnCount(2);
1203     sheet.setRowCount(5);
1204 
1205     auto* col0{sheet.column(0)};
1206     col0->setColumnMode(AbstractColumn::ColumnMode::Double);
1207     col0->setValueAt(0, 0.);
1208     // missing value for row = 1
1209     col0->setValueAt(2, 2.);
1210     col0->setValueAt(3, 3.);
1211 
1212     auto* col1{sheet.column(1)};
1213     col1->setColumnMode(AbstractColumn::ColumnMode::Double);
1214     col1->setValueAt(0, 0.);
1215     col1->setValueAt(1, 1.);
1216     // missing value for row = 2
1217     col1->setValueAt(3, 3.);
1218 
1219     // remove rows with empty values and check the results
1220     sheet.removeEmptyRows();
1221     QCOMPARE(sheet.rowCount(), 2);
1222     QCOMPARE(col0->valueAt(0), 0.);
1223     QCOMPARE(col0->valueAt(1), 3.);
1224     QCOMPARE(col1->valueAt(0), 0.);
1225     QCOMPARE(col1->valueAt(1), 3.);
1226 }
1227 
1228 void SpreadsheetTest::testMaskRowsWithMissingValues() {
1229     // prepare the spreadsheet
1230     Spreadsheet sheet(QStringLiteral("test"), false);
1231     sheet.setColumnCount(2);
1232     sheet.setRowCount(5);
1233 
1234     auto* col0{sheet.column(0)};
1235     col0->setColumnMode(AbstractColumn::ColumnMode::Double);
1236     col0->setValueAt(0, 0.);
1237     // missing value for row = 1
1238     col0->setValueAt(2, 2.);
1239     col0->setValueAt(3, 3.);
1240 
1241     auto* col1{sheet.column(1)};
1242     col1->setColumnMode(AbstractColumn::ColumnMode::Double);
1243     col1->setValueAt(0, 0.);
1244     col1->setValueAt(1, 1.);
1245     // missing value for row = 2
1246     col1->setValueAt(3, 3.);
1247 
1248     // mask rows with empty values and check the results
1249     sheet.maskEmptyRows();
1250     QCOMPARE(sheet.rowCount(), 5);
1251     QCOMPARE(col0->isMasked(0), false);
1252     QCOMPARE(col0->isMasked(1), true);
1253     QCOMPARE(col0->isMasked(2), true);
1254     QCOMPARE(col0->isMasked(3), false);
1255     QCOMPARE(col0->isMasked(4), true);
1256     QCOMPARE(col1->isMasked(0), false);
1257     QCOMPARE(col1->isMasked(1), true);
1258     QCOMPARE(col1->isMasked(2), true);
1259     QCOMPARE(col1->isMasked(3), false);
1260     QCOMPARE(col1->isMasked(4), true);
1261 }
1262 
1263 // **********************************************************
1264 // ********************* flattening  ************************
1265 // **********************************************************
1266 void SpreadsheetTest::testFlatten00() {
1267     Project project;
1268     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1269     project.addChild(sheet);
1270     sheet->setColumnCount(5);
1271     sheet->setRowCount(4);
1272 
1273     // "Year"
1274     auto* col1 = sheet->column(0);
1275     col1->setName(QStringLiteral("Year"));
1276     col1->setColumnMode(AbstractColumn::ColumnMode::Text);
1277     col1->setTextAt(0, QStringLiteral("2021"));
1278     col1->setTextAt(1, QStringLiteral("2022"));
1279     col1->setTextAt(2, QStringLiteral("2021"));
1280     col1->setTextAt(3, QStringLiteral("2022"));
1281 
1282     // "Country"
1283     auto* col2 = sheet->column(1);
1284     col2->setName(QStringLiteral("Country"));
1285     col2->setColumnMode(AbstractColumn::ColumnMode::Text);
1286     col2->setTextAt(0, QStringLiteral("Germany"));
1287     col2->setTextAt(1, QStringLiteral("Germany"));
1288     col2->setTextAt(2, QStringLiteral("Poland"));
1289     col2->setTextAt(3, QStringLiteral("Poland"));
1290 
1291     // "Sales for Product 1"
1292     auto* col3 = sheet->column(2);
1293     col3->setName(QStringLiteral("Product 1"));
1294     col3->setColumnMode(AbstractColumn::ColumnMode::Integer);
1295     col3->setIntegerAt(0, 1);
1296     col3->setIntegerAt(1, 10);
1297     col3->setIntegerAt(2, 4);
1298     col3->setIntegerAt(3, 40);
1299 
1300     // "Sales for Product 2"
1301     auto* col4 = sheet->column(3);
1302     col4->setName(QStringLiteral("Product 2"));
1303     col4->setColumnMode(AbstractColumn::ColumnMode::Integer);
1304     col4->setIntegerAt(0, 2);
1305     col4->setIntegerAt(1, 20);
1306     col4->setIntegerAt(2, 5);
1307     col4->setIntegerAt(3, 50);
1308 
1309     // "Sales for Product 3"
1310     auto* col5 = sheet->column(4);
1311     col5->setName(QStringLiteral("Product 3"));
1312     col5->setColumnMode(AbstractColumn::ColumnMode::Integer);
1313     col5->setIntegerAt(0, 3);
1314     col5->setIntegerAt(1, 30);
1315     col5->setIntegerAt(2, 6);
1316     col5->setIntegerAt(3, 60);
1317 
1318     // flatten the product columns relatively to year and country
1319     FlattenColumnsDialog dlg(sheet);
1320     QVector<Column*> referenceColumns;
1321     referenceColumns << col1;
1322     referenceColumns << col2;
1323 
1324     QVector<Column*> valueColumns;
1325     valueColumns << col3;
1326     valueColumns << col4;
1327     valueColumns << col5;
1328 
1329     dlg.flatten(sheet, valueColumns, referenceColumns);
1330 
1331     // checks
1332     // make sure a new target spreadsheet with the flattened data was created
1333     const auto& sheets = project.children<Spreadsheet>();
1334     QCOMPARE(sheets.count(), 2);
1335     auto* targetSheet = sheets.at(1);
1336     QCOMPARE(targetSheet->columnCount(), 4); // two reference columns, column "Category" and column "Value"
1337     QCOMPARE(targetSheet->rowCount(), 12);
1338 
1339     // check values
1340     col1 = targetSheet->column(0);
1341     QCOMPARE(col1->textAt(0), QStringLiteral("2021"));
1342     QCOMPARE(col1->textAt(1), QStringLiteral("2021"));
1343     QCOMPARE(col1->textAt(2), QStringLiteral("2021"));
1344     QCOMPARE(col1->textAt(3), QStringLiteral("2022"));
1345     QCOMPARE(col1->textAt(4), QStringLiteral("2022"));
1346     QCOMPARE(col1->textAt(5), QStringLiteral("2022"));
1347     QCOMPARE(col1->textAt(6), QStringLiteral("2021"));
1348     QCOMPARE(col1->textAt(7), QStringLiteral("2021"));
1349     QCOMPARE(col1->textAt(8), QStringLiteral("2021"));
1350     QCOMPARE(col1->textAt(9), QStringLiteral("2022"));
1351     QCOMPARE(col1->textAt(10), QStringLiteral("2022"));
1352     QCOMPARE(col1->textAt(11), QStringLiteral("2022"));
1353 
1354     col4 = targetSheet->column(3);
1355     QCOMPARE(col4->integerAt(0), 1);
1356     QCOMPARE(col4->integerAt(1), 2);
1357     QCOMPARE(col4->integerAt(2), 3);
1358     QCOMPARE(col4->integerAt(3), 10);
1359     QCOMPARE(col4->integerAt(4), 20);
1360     QCOMPARE(col4->integerAt(5), 30);
1361     QCOMPARE(col4->integerAt(6), 4);
1362     QCOMPARE(col4->integerAt(7), 5);
1363     QCOMPARE(col4->integerAt(8), 6);
1364     QCOMPARE(col4->integerAt(9), 40);
1365     QCOMPARE(col4->integerAt(10), 50);
1366     QCOMPARE(col4->integerAt(11), 60);
1367 }
1368 
1369 // test with a missing value in one of the reference columns
1370 void SpreadsheetTest::testFlatten01() {
1371     Project project;
1372     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1373     project.addChild(sheet);
1374     sheet->setColumnCount(5);
1375     sheet->setRowCount(2);
1376 
1377     // "Year"
1378     auto* col1 = sheet->column(0);
1379     col1->setName(QStringLiteral("Year"));
1380     col1->setColumnMode(AbstractColumn::ColumnMode::Text);
1381     col1->setTextAt(0, QStringLiteral("2021"));
1382     col1->setTextAt(1, QStringLiteral("2022"));
1383 
1384     // "Country"
1385     auto* col2 = sheet->column(1);
1386     col2->setName(QStringLiteral("Country"));
1387     col2->setColumnMode(AbstractColumn::ColumnMode::Text);
1388     col2->setTextAt(0, QStringLiteral("Germany"));
1389     // missing value in the second row
1390 
1391     // "Sales for Product 1"
1392     auto* col3 = sheet->column(2);
1393     col3->setName(QStringLiteral("Product 1"));
1394     col3->setColumnMode(AbstractColumn::ColumnMode::Integer);
1395     col3->setIntegerAt(0, 1);
1396     col3->setIntegerAt(1, 10);
1397 
1398     // "Sales for Product 2"
1399     auto* col4 = sheet->column(3);
1400     col4->setName(QStringLiteral("Product 2"));
1401     col4->setColumnMode(AbstractColumn::ColumnMode::Integer);
1402     col4->setIntegerAt(0, 2);
1403     col4->setIntegerAt(1, 20);
1404 
1405     // "Sales for Product 3"
1406     auto* col5 = sheet->column(4);
1407     col5->setName(QStringLiteral("Product 3"));
1408     col5->setColumnMode(AbstractColumn::ColumnMode::Integer);
1409     col5->setIntegerAt(0, 3);
1410     col5->setIntegerAt(1, 30);
1411 
1412     // flatten the product columns relatively to year and country
1413     FlattenColumnsDialog dlg(sheet);
1414     QVector<Column*> referenceColumns;
1415     referenceColumns << col1;
1416     referenceColumns << col2;
1417 
1418     QVector<Column*> valueColumns;
1419     valueColumns << col3;
1420     valueColumns << col4;
1421     valueColumns << col5;
1422 
1423     dlg.flatten(sheet, valueColumns, referenceColumns);
1424 
1425     // checks
1426     // make sure a new target spreadsheet with the flattened data was created
1427     const auto& sheets = project.children<Spreadsheet>();
1428     QCOMPARE(sheets.count(), 2);
1429     auto* targetSheet = sheets.at(1);
1430     QCOMPARE(targetSheet->columnCount(), 4); // two reference columns, column "Category" and column "Value"
1431     QCOMPARE(targetSheet->rowCount(), 6);
1432 
1433     // check values
1434     col1 = targetSheet->column(0);
1435     QCOMPARE(col1->textAt(0), QStringLiteral("2021"));
1436     QCOMPARE(col1->textAt(1), QStringLiteral("2021"));
1437     QCOMPARE(col1->textAt(2), QStringLiteral("2021"));
1438     QCOMPARE(col1->textAt(3), QStringLiteral("2022"));
1439     QCOMPARE(col1->textAt(4), QStringLiteral("2022"));
1440     QCOMPARE(col1->textAt(5), QStringLiteral("2022"));
1441 
1442     col1 = targetSheet->column(1);
1443     QCOMPARE(col1->textAt(0), QStringLiteral("Germany"));
1444     QCOMPARE(col1->textAt(1), QStringLiteral("Germany"));
1445     QCOMPARE(col1->textAt(2), QStringLiteral("Germany"));
1446     QCOMPARE(col1->textAt(3), QString());
1447     QCOMPARE(col1->textAt(4), QString());
1448     QCOMPARE(col1->textAt(5), QString());
1449 
1450     col4 = targetSheet->column(3);
1451     QCOMPARE(col4->integerAt(0), 1);
1452     QCOMPARE(col4->integerAt(1), 2);
1453     QCOMPARE(col4->integerAt(2), 3);
1454     QCOMPARE(col4->integerAt(3), 10);
1455     QCOMPARE(col4->integerAt(4), 20);
1456     QCOMPARE(col4->integerAt(5), 30);
1457 }
1458 
1459 // test with missing values in the reference columns - no result should be produced for these rows
1460 void SpreadsheetTest::testFlatten02() {
1461     Project project;
1462     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1463     project.addChild(sheet);
1464     sheet->setColumnCount(5);
1465     sheet->setRowCount(2);
1466 
1467     // "Year"
1468     auto* col1 = sheet->column(0);
1469     col1->setName(QStringLiteral("Year"));
1470     col1->setColumnMode(AbstractColumn::ColumnMode::Text);
1471     col1->setTextAt(0, QStringLiteral("2021"));
1472     // missing value in the second row
1473 
1474     // "Country"
1475     auto* col2 = sheet->column(1);
1476     col2->setName(QStringLiteral("Country"));
1477     col2->setColumnMode(AbstractColumn::ColumnMode::Text);
1478     col2->setTextAt(0, QStringLiteral("Germany"));
1479     // missing value in the second rows
1480 
1481     // "Sales for Product 1"
1482     auto* col3 = sheet->column(2);
1483     col3->setName(QStringLiteral("Product 1"));
1484     col3->setColumnMode(AbstractColumn::ColumnMode::Integer);
1485     col3->setIntegerAt(0, 1);
1486     col3->setIntegerAt(1, 10);
1487 
1488     // "Sales for Product 2"
1489     auto* col4 = sheet->column(3);
1490     col4->setName(QStringLiteral("Product 2"));
1491     col4->setColumnMode(AbstractColumn::ColumnMode::Integer);
1492     col4->setIntegerAt(0, 2);
1493     col4->setIntegerAt(1, 20);
1494 
1495     // "Sales for Product 3"
1496     auto* col5 = sheet->column(4);
1497     col5->setName(QStringLiteral("Product 3"));
1498     col5->setColumnMode(AbstractColumn::ColumnMode::Integer);
1499     col5->setIntegerAt(0, 3);
1500     col5->setIntegerAt(1, 30);
1501 
1502     // flatten the product columns relatively to year and country
1503     FlattenColumnsDialog dlg(sheet);
1504     QVector<Column*> referenceColumns;
1505     referenceColumns << col1;
1506     referenceColumns << col2;
1507 
1508     QVector<Column*> valueColumns;
1509     valueColumns << col3;
1510     valueColumns << col4;
1511     valueColumns << col5;
1512 
1513     dlg.flatten(sheet, valueColumns, referenceColumns);
1514 
1515     // checks
1516     // make sure a new target spreadsheet with the flattened data was created
1517     const auto& sheets = project.children<Spreadsheet>();
1518     QCOMPARE(sheets.count(), 2);
1519     auto* targetSheet = sheets.at(1);
1520     QCOMPARE(targetSheet->columnCount(), 4); // two reference columns, column "Category" and column "Value"
1521     QCOMPARE(targetSheet->rowCount(), 3);
1522 
1523     // check values
1524     col1 = targetSheet->column(0);
1525     QCOMPARE(col1->textAt(0), QStringLiteral("2021"));
1526     QCOMPARE(col1->textAt(1), QStringLiteral("2021"));
1527     QCOMPARE(col1->textAt(2), QStringLiteral("2021"));
1528 
1529     col1 = targetSheet->column(1);
1530     QCOMPARE(col1->textAt(0), QStringLiteral("Germany"));
1531     QCOMPARE(col1->textAt(1), QStringLiteral("Germany"));
1532     QCOMPARE(col1->textAt(2), QStringLiteral("Germany"));
1533 
1534     col4 = targetSheet->column(3);
1535     QCOMPARE(col4->integerAt(0), 1);
1536     QCOMPARE(col4->integerAt(1), 2);
1537     QCOMPARE(col4->integerAt(2), 3);
1538 }
1539 
1540 // test with missing no reference columns
1541 void SpreadsheetTest::testFlatten03() {
1542     Project project;
1543     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1544     project.addChild(sheet);
1545     sheet->setColumnCount(3);
1546     sheet->setRowCount(2);
1547 
1548     // "Sales for Product 1"
1549     auto* col1 = sheet->column(0);
1550     col1->setName(QStringLiteral("Product 1"));
1551     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
1552     col1->setIntegerAt(0, 1);
1553     col1->setIntegerAt(1, 10);
1554 
1555     // "Sales for Product 2"
1556     auto* col2 = sheet->column(1);
1557     col2->setName(QStringLiteral("Product 2"));
1558     col2->setColumnMode(AbstractColumn::ColumnMode::Integer);
1559     col2->setIntegerAt(0, 2);
1560     col2->setIntegerAt(1, 20);
1561 
1562     // "Sales for Product 3"
1563     auto* col3 = sheet->column(2);
1564     col3->setName(QStringLiteral("Product 3"));
1565     col3->setColumnMode(AbstractColumn::ColumnMode::Integer);
1566     col3->setIntegerAt(0, 3);
1567     col3->setIntegerAt(1, 30);
1568 
1569     // flatten the product columns without any reference columns
1570     QVector<Column*> valueColumns;
1571     valueColumns << col1;
1572     valueColumns << col2;
1573     valueColumns << col3;
1574 
1575     FlattenColumnsDialog dlg(sheet);
1576     dlg.flatten(sheet, valueColumns, QVector<Column*>());
1577 
1578     // checks
1579     // make sure a new target spreadsheet with the flattened data was created
1580     const auto& sheets = project.children<Spreadsheet>();
1581     QCOMPARE(sheets.count(), 2);
1582     auto* targetSheet = sheets.at(1);
1583     QCOMPARE(targetSheet->columnCount(), 2); // no reference columns, only column "Category" and column "Value"
1584     QCOMPARE(targetSheet->rowCount(), 6);
1585 
1586     // check values
1587     col1 = targetSheet->column(0);
1588     QCOMPARE(col1->textAt(0), QStringLiteral("Product 1"));
1589     QCOMPARE(col1->textAt(1), QStringLiteral("Product 2"));
1590     QCOMPARE(col1->textAt(2), QStringLiteral("Product 3"));
1591     QCOMPARE(col1->textAt(3), QStringLiteral("Product 1"));
1592     QCOMPARE(col1->textAt(4), QStringLiteral("Product 2"));
1593     QCOMPARE(col1->textAt(5), QStringLiteral("Product 3"));
1594 
1595     col2 = targetSheet->column(1);
1596     QCOMPARE(col2->integerAt(0), 1);
1597     QCOMPARE(col2->integerAt(1), 2);
1598     QCOMPARE(col2->integerAt(2), 3);
1599     QCOMPARE(col2->integerAt(3), 10);
1600     QCOMPARE(col2->integerAt(4), 20);
1601     QCOMPARE(col2->integerAt(5), 30);
1602 }
1603 
1604 // **********************************************************
1605 // ******************** search&replace  *********************
1606 // **********************************************************
1607 Spreadsheet* SpreadsheetTest::createSearchReplaceSpreadsheet() {
1608     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1609 
1610     sheet->setColumnCount(4);
1611     sheet->setRowCount(4);
1612 
1613     // first text column
1614     auto* col1 = sheet->column(0);
1615     col1->setName(QStringLiteral("text"));
1616     col1->setColumnMode(AbstractColumn::ColumnMode::Text);
1617     col1->setTextAt(0, QStringLiteral("A"));
1618     col1->setTextAt(1, QStringLiteral("B"));
1619     col1->setTextAt(2, QStringLiteral("A"));
1620     col1->setTextAt(3, QStringLiteral("C"));
1621 
1622     // first numeric (integer) column
1623     auto* col2 = sheet->column(1);
1624     col2->setName(QStringLiteral("integer"));
1625     col2->setColumnMode(AbstractColumn::ColumnMode::Integer);
1626     col2->setIntegerAt(0, 1);
1627     col2->setIntegerAt(1, 2);
1628     col2->setIntegerAt(2, 4);
1629     col2->setIntegerAt(3, 2);
1630 
1631     // second text column
1632     auto* col3 = sheet->column(2);
1633     col3->setName(QStringLiteral("text2"));
1634     col3->setColumnMode(AbstractColumn::ColumnMode::Text);
1635     col3->setTextAt(0, QStringLiteral("B1"));
1636     col3->setTextAt(1, QStringLiteral("A"));
1637     col3->setTextAt(2, QStringLiteral("C2"));
1638     col3->setTextAt(3, QStringLiteral("A2"));
1639 
1640     // second numeric (double) column
1641     auto* col4 = sheet->column(3);
1642     col4->setName(QStringLiteral("double"));
1643     col4->setColumnMode(AbstractColumn::ColumnMode::Double);
1644     col4->setValueAt(0, 4);
1645     col4->setValueAt(1, 3);
1646     col4->setValueAt(2, 2);
1647     col4->setValueAt(3, 1);
1648 
1649     return sheet;
1650 }
1651 
1652 /*!
1653  * simple search ignoring data types, column-major order
1654  */
1655 void SpreadsheetTest::testSearchSimple00() {
1656     Project project;
1657     auto* sheet = createSearchReplaceSpreadsheet();
1658     project.addChild(sheet);
1659 
1660     // navigate to the (0,0) cell having the text value "A"
1661     auto* view = static_cast<SpreadsheetView*>(sheet->view());
1662     view->goToCell(0, 0);
1663 
1664     // initialize the search&replace widget
1665     auto* searchReplaceWidget = new SearchReplaceWidget(sheet, view);
1666     searchReplaceWidget->setReplaceEnabled(false);
1667 
1668     auto indexes = view->selectionModel()->selectedIndexes();
1669     if (!indexes.isEmpty()) {
1670         const auto& firstIndex = indexes.constFirst();
1671         const auto* column = sheet->column(firstIndex.column());
1672         const int row = firstIndex.row();
1673         searchReplaceWidget->setInitialPattern(column->columnMode(), column->asStringColumn()->textAt(row));
1674     }
1675 
1676     // checks: the initial cell text is "A", we navigate with 'next'
1677     // and then back with 'prev' in the column-major order looking for "A"
1678 
1679     // initial
1680     indexes = view->selectionModel()->selectedIndexes();
1681     QCOMPARE(indexes.count(), 1);
1682     auto curIndex = indexes.constFirst();
1683     QCOMPARE(curIndex.row(), 0);
1684     QCOMPARE(curIndex.column(), 0);
1685 
1686     // next
1687     searchReplaceWidget->findNextSimple(true);
1688     indexes = view->selectionModel()->selectedIndexes();
1689     QCOMPARE(indexes.count(), 1);
1690     curIndex = indexes.constFirst();
1691     QCOMPARE(curIndex.row(), 2);
1692     QCOMPARE(curIndex.column(), 0);
1693 
1694     // next
1695     searchReplaceWidget->findNextSimple(true);
1696     indexes = view->selectionModel()->selectedIndexes();
1697     QCOMPARE(indexes.count(), 1);
1698     curIndex = indexes.constFirst();
1699     QCOMPARE(curIndex.row(), 1);
1700     QCOMPARE(curIndex.column(), 2);
1701 
1702     // next
1703     searchReplaceWidget->findNextSimple(true);
1704     indexes = view->selectionModel()->selectedIndexes();
1705     QCOMPARE(indexes.count(), 1);
1706     curIndex = indexes.constFirst();
1707     QCOMPARE(curIndex.row(), 3);
1708     QCOMPARE(curIndex.column(), 2);
1709 
1710     // next, last matching cell reached
1711     searchReplaceWidget->findNextSimple(true);
1712     indexes = view->selectionModel()->selectedIndexes();
1713     QCOMPARE(indexes.count(), 1);
1714     curIndex = indexes.constFirst();
1715     QCOMPARE(curIndex.row(), 3);
1716     QCOMPARE(curIndex.column(), 2);
1717 
1718     // previous
1719     searchReplaceWidget->findPreviousSimple(true);
1720     indexes = view->selectionModel()->selectedIndexes();
1721     QCOMPARE(indexes.count(), 1);
1722     curIndex = indexes.constFirst();
1723     QCOMPARE(curIndex.row(), 1);
1724     QCOMPARE(curIndex.column(), 2);
1725 
1726     // previous
1727     searchReplaceWidget->findPreviousSimple(true);
1728     indexes = view->selectionModel()->selectedIndexes();
1729     QCOMPARE(indexes.count(), 1);
1730     curIndex = indexes.constFirst();
1731     QCOMPARE(curIndex.row(), 2);
1732     QCOMPARE(curIndex.column(), 0);
1733 
1734     // previous
1735     searchReplaceWidget->findPreviousSimple(true);
1736     indexes = view->selectionModel()->selectedIndexes();
1737     QCOMPARE(indexes.count(), 1);
1738     curIndex = indexes.constFirst();
1739     QCOMPARE(curIndex.row(), 0);
1740     QCOMPARE(curIndex.column(), 0);
1741 
1742     // previous, last matching cell reached
1743     searchReplaceWidget->findPreviousSimple(true);
1744     indexes = view->selectionModel()->selectedIndexes();
1745     QCOMPARE(indexes.count(), 1);
1746     curIndex = indexes.constFirst();
1747     QCOMPARE(curIndex.row(), 0);
1748     QCOMPARE(curIndex.column(), 0);
1749 }
1750 
1751 /*!
1752  * extended search for Text, column-major order
1753  */
1754 void SpreadsheetTest::testSearchExtended00() {
1755     Project project;
1756     auto* sheet = createSearchReplaceSpreadsheet();
1757     project.addChild(sheet);
1758 
1759     // navigate to the (0,0) cell having the text value "A"
1760     auto* view = static_cast<SpreadsheetView*>(sheet->view());
1761     view->goToCell(0, 0);
1762 
1763     // initialize the search&replace widget
1764     auto* searchReplaceWidget = new SearchReplaceWidget(sheet, view);
1765     searchReplaceWidget->setReplaceEnabled(true);
1766     searchReplaceWidget->setDataType(SearchReplaceWidget::DataType::Text);
1767     searchReplaceWidget->setOrder(SearchReplaceWidget::Order::ColumnMajor);
1768 
1769     auto indexes = view->selectionModel()->selectedIndexes();
1770     if (!indexes.isEmpty()) {
1771         const auto& firstIndex = indexes.constFirst();
1772         const auto* column = sheet->column(firstIndex.column());
1773         const int row = firstIndex.row();
1774         searchReplaceWidget->setInitialPattern(column->columnMode(), column->asStringColumn()->textAt(row));
1775     }
1776 
1777     // checks: the initial cell text is "A", we navigate with 'next'
1778     // and then back with 'prev' in the column-major order looking for "A"
1779 
1780     // initial
1781     indexes = view->selectionModel()->selectedIndexes();
1782     QCOMPARE(indexes.count(), 1);
1783     auto curIndex = indexes.constFirst();
1784     QCOMPARE(curIndex.row(), 0);
1785     QCOMPARE(curIndex.column(), 0);
1786 
1787     // next
1788     searchReplaceWidget->findNext(true);
1789     indexes = view->selectionModel()->selectedIndexes();
1790     QCOMPARE(indexes.count(), 1);
1791     curIndex = indexes.constFirst();
1792     QCOMPARE(curIndex.row(), 2);
1793     QCOMPARE(curIndex.column(), 0);
1794 
1795     // next
1796     searchReplaceWidget->findNext(true);
1797     indexes = view->selectionModel()->selectedIndexes();
1798     QCOMPARE(indexes.count(), 1);
1799     curIndex = indexes.constFirst();
1800     QCOMPARE(curIndex.row(), 1);
1801     QCOMPARE(curIndex.column(), 2);
1802 
1803     // next, last matching cell reached
1804     searchReplaceWidget->findNext(true);
1805     indexes = view->selectionModel()->selectedIndexes();
1806     QCOMPARE(indexes.count(), 1);
1807     curIndex = indexes.constFirst();
1808     QCOMPARE(curIndex.row(), 1);
1809     QCOMPARE(curIndex.column(), 2);
1810 
1811     // previous
1812     searchReplaceWidget->findPrevious(true);
1813     indexes = view->selectionModel()->selectedIndexes();
1814     QCOMPARE(indexes.count(), 1);
1815     curIndex = indexes.constFirst();
1816     QCOMPARE(curIndex.row(), 2);
1817     QCOMPARE(curIndex.column(), 0);
1818 
1819     // previous
1820     searchReplaceWidget->findPrevious(true);
1821     indexes = view->selectionModel()->selectedIndexes();
1822     QCOMPARE(indexes.count(), 1);
1823     curIndex = indexes.constFirst();
1824     QCOMPARE(curIndex.row(), 0);
1825     QCOMPARE(curIndex.column(), 0);
1826 
1827     // previous, last matching cell reached
1828     searchReplaceWidget->findPrevious(true);
1829     indexes = view->selectionModel()->selectedIndexes();
1830     QCOMPARE(indexes.count(), 1);
1831     curIndex = indexes.constFirst();
1832     QCOMPARE(curIndex.row(), 0);
1833     QCOMPARE(curIndex.column(), 0);
1834 }
1835 
1836 /*!
1837  * extended search for Text, row-major order
1838  */
1839 void SpreadsheetTest::testSearchExtended01() {
1840     Project project;
1841     auto* sheet = createSearchReplaceSpreadsheet();
1842     project.addChild(sheet);
1843 
1844     // navigate to the (0,0) cell having the text value "A"
1845     auto* view = static_cast<SpreadsheetView*>(sheet->view());
1846     view->goToCell(0, 0);
1847 
1848     // initialize the search&replace widget
1849     auto* searchReplaceWidget = new SearchReplaceWidget(sheet, view);
1850     searchReplaceWidget->setReplaceEnabled(true);
1851     searchReplaceWidget->setDataType(SearchReplaceWidget::DataType::Text);
1852     searchReplaceWidget->setOrder(SearchReplaceWidget::Order::RowMajor);
1853 
1854     auto indexes = view->selectionModel()->selectedIndexes();
1855     if (!indexes.isEmpty()) {
1856         const auto& firstIndex = indexes.constFirst();
1857         const auto* column = sheet->column(firstIndex.column());
1858         const int row = firstIndex.row();
1859         searchReplaceWidget->setInitialPattern(column->columnMode(), column->asStringColumn()->textAt(row));
1860     }
1861 
1862     // checks: the initial cell text is "A", we navigate with 'next'
1863     // and then back with 'prev' in the row-major order looking for "A"
1864 
1865     // initial
1866     indexes = view->selectionModel()->selectedIndexes();
1867     QCOMPARE(indexes.count(), 1);
1868     auto curIndex = indexes.constFirst();
1869     QCOMPARE(curIndex.row(), 0);
1870     QCOMPARE(curIndex.column(), 0);
1871 
1872     // next
1873     searchReplaceWidget->findNext(true);
1874     indexes = view->selectionModel()->selectedIndexes();
1875     QCOMPARE(indexes.count(), 1);
1876     curIndex = indexes.constFirst();
1877     QCOMPARE(curIndex.row(), 1);
1878     QCOMPARE(curIndex.column(), 2);
1879 
1880     // next
1881     searchReplaceWidget->findNext(true);
1882     indexes = view->selectionModel()->selectedIndexes();
1883     QCOMPARE(indexes.count(), 1);
1884     curIndex = indexes.constFirst();
1885     QCOMPARE(curIndex.row(), 2);
1886     QCOMPARE(curIndex.column(), 0);
1887 
1888     // next, last matching cell reached
1889     searchReplaceWidget->findNext(true);
1890     indexes = view->selectionModel()->selectedIndexes();
1891     QCOMPARE(indexes.count(), 1);
1892     curIndex = indexes.constFirst();
1893     QCOMPARE(curIndex.row(), 2);
1894     QCOMPARE(curIndex.column(), 0);
1895 
1896     // previous
1897     searchReplaceWidget->findPrevious(true);
1898     indexes = view->selectionModel()->selectedIndexes();
1899     QCOMPARE(indexes.count(), 1);
1900     curIndex = indexes.constFirst();
1901     QCOMPARE(curIndex.row(), 1);
1902     QCOMPARE(curIndex.column(), 2);
1903 
1904     // previous
1905     searchReplaceWidget->findPrevious(true);
1906     indexes = view->selectionModel()->selectedIndexes();
1907     QCOMPARE(indexes.count(), 1);
1908     curIndex = indexes.constFirst();
1909     QCOMPARE(curIndex.row(), 0);
1910     QCOMPARE(curIndex.column(), 0);
1911 
1912     // previous, last matching cell reached
1913     searchReplaceWidget->findPrevious(true);
1914     indexes = view->selectionModel()->selectedIndexes();
1915     QCOMPARE(indexes.count(), 1);
1916     curIndex = indexes.constFirst();
1917     QCOMPARE(curIndex.row(), 0);
1918     QCOMPARE(curIndex.column(), 0);
1919 }
1920 
1921 /*!
1922  * search for Numeric, column-major order
1923  */
1924 void SpreadsheetTest::testSearchExtended02() {
1925     Project project;
1926     auto* sheet = createSearchReplaceSpreadsheet();
1927     project.addChild(sheet);
1928 
1929     // navigate to the (1,1) cell having the numeric value "2"
1930     auto* view = static_cast<SpreadsheetView*>(sheet->view());
1931     view->goToCell(1, 1);
1932 
1933     // initialize the search&replace widget
1934     auto* searchReplaceWidget = new SearchReplaceWidget(sheet, view);
1935     searchReplaceWidget->setReplaceEnabled(true);
1936     searchReplaceWidget->setDataType(SearchReplaceWidget::DataType::Numeric);
1937     searchReplaceWidget->setOrder(SearchReplaceWidget::Order::ColumnMajor);
1938 
1939     auto indexes = view->selectionModel()->selectedIndexes();
1940     if (!indexes.isEmpty()) {
1941         const auto& firstIndex = indexes.constFirst();
1942         const auto* column = sheet->column(firstIndex.column());
1943         const int row = firstIndex.row();
1944         searchReplaceWidget->setInitialPattern(column->columnMode(), column->asStringColumn()->textAt(row));
1945     }
1946 
1947     // checks: the initial cell text is "2", we navigate with 'next'
1948     // and then back with 'prev' in the column-major order looking for "2"
1949 
1950     // initial
1951     indexes = view->selectionModel()->selectedIndexes();
1952     QCOMPARE(indexes.count(), 1);
1953     auto curIndex = indexes.constFirst();
1954     QCOMPARE(curIndex.row(), 1);
1955     QCOMPARE(curIndex.column(), 1);
1956 
1957     // next
1958     searchReplaceWidget->findNext(true);
1959     indexes = view->selectionModel()->selectedIndexes();
1960     QCOMPARE(indexes.count(), 1);
1961     curIndex = indexes.constFirst();
1962     QCOMPARE(curIndex.row(), 3);
1963     QCOMPARE(curIndex.column(), 1);
1964 
1965     // next
1966     searchReplaceWidget->findNext(true);
1967     indexes = view->selectionModel()->selectedIndexes();
1968     QCOMPARE(indexes.count(), 1);
1969     curIndex = indexes.constFirst();
1970     QCOMPARE(curIndex.row(), 2);
1971     QCOMPARE(curIndex.column(), 3);
1972 
1973     // next, last matching cell reached
1974     searchReplaceWidget->findNext(true);
1975     indexes = view->selectionModel()->selectedIndexes();
1976     QCOMPARE(indexes.count(), 1);
1977     curIndex = indexes.constFirst();
1978     QCOMPARE(curIndex.row(), 2);
1979     QCOMPARE(curIndex.column(), 3);
1980 
1981     // previous
1982     searchReplaceWidget->findPrevious(true);
1983     indexes = view->selectionModel()->selectedIndexes();
1984     QCOMPARE(indexes.count(), 1);
1985     curIndex = indexes.constFirst();
1986     QCOMPARE(curIndex.row(), 3);
1987     QCOMPARE(curIndex.column(), 1);
1988 
1989     // previous
1990     searchReplaceWidget->findPrevious(true);
1991     indexes = view->selectionModel()->selectedIndexes();
1992     QCOMPARE(indexes.count(), 1);
1993     curIndex = indexes.constFirst();
1994     QCOMPARE(curIndex.row(), 1);
1995     QCOMPARE(curIndex.column(), 1);
1996 
1997     // previous, last matching cell reached
1998     searchReplaceWidget->findPrevious(true);
1999     indexes = view->selectionModel()->selectedIndexes();
2000     QCOMPARE(indexes.count(), 1);
2001     curIndex = indexes.constFirst();
2002     QCOMPARE(curIndex.row(), 1);
2003     QCOMPARE(curIndex.column(), 1);
2004 }
2005 
2006 /*!
2007  * search for Numeric, row major
2008  */
2009 void SpreadsheetTest::testSearchExtended03() {
2010     Project project;
2011     auto* sheet = createSearchReplaceSpreadsheet();
2012     project.addChild(sheet);
2013 
2014     // navigate to the (1,1) cell having the numeric value "2"
2015     auto* view = static_cast<SpreadsheetView*>(sheet->view());
2016     view->goToCell(1, 1);
2017 
2018     // initialize the search&replace widget
2019     auto* searchReplaceWidget = new SearchReplaceWidget(sheet, view);
2020     searchReplaceWidget->setReplaceEnabled(true);
2021     searchReplaceWidget->setDataType(SearchReplaceWidget::DataType::Numeric);
2022     searchReplaceWidget->setOrder(SearchReplaceWidget::Order::RowMajor);
2023 
2024     auto indexes = view->selectionModel()->selectedIndexes();
2025     if (!indexes.isEmpty()) {
2026         const auto& firstIndex = indexes.constFirst();
2027         const auto* column = sheet->column(firstIndex.column());
2028         const int row = firstIndex.row();
2029         searchReplaceWidget->setInitialPattern(column->columnMode(), column->asStringColumn()->textAt(row));
2030     }
2031 
2032     // checks: the initial cell text is "2", we navigate with 'next'
2033     // and then back with 'prev' in the column-major order looking for "2"
2034 
2035     // initial
2036     indexes = view->selectionModel()->selectedIndexes();
2037     QCOMPARE(indexes.count(), 1);
2038     auto curIndex = indexes.constFirst();
2039     QCOMPARE(curIndex.row(), 1);
2040     QCOMPARE(curIndex.column(), 1);
2041 
2042     // next
2043     searchReplaceWidget->findNext(true);
2044     indexes = view->selectionModel()->selectedIndexes();
2045     QCOMPARE(indexes.count(), 1);
2046     curIndex = indexes.constFirst();
2047     QCOMPARE(curIndex.row(), 2);
2048     QCOMPARE(curIndex.column(), 3);
2049 
2050     // next
2051     searchReplaceWidget->findNext(true);
2052     indexes = view->selectionModel()->selectedIndexes();
2053     QCOMPARE(indexes.count(), 1);
2054     curIndex = indexes.constFirst();
2055     QCOMPARE(curIndex.row(), 3);
2056     QCOMPARE(curIndex.column(), 1);
2057 
2058     // next, last matching cell reached
2059     searchReplaceWidget->findNext(true);
2060     indexes = view->selectionModel()->selectedIndexes();
2061     QCOMPARE(indexes.count(), 1);
2062     curIndex = indexes.constFirst();
2063     QCOMPARE(curIndex.row(), 3);
2064     QCOMPARE(curIndex.column(), 1);
2065 
2066     // previous
2067     searchReplaceWidget->findPrevious(true);
2068     indexes = view->selectionModel()->selectedIndexes();
2069     QCOMPARE(indexes.count(), 1);
2070     curIndex = indexes.constFirst();
2071     QCOMPARE(curIndex.row(), 2);
2072     QCOMPARE(curIndex.column(), 3);
2073 
2074     // previous
2075     searchReplaceWidget->findPrevious(true);
2076     indexes = view->selectionModel()->selectedIndexes();
2077     QCOMPARE(indexes.count(), 1);
2078     curIndex = indexes.constFirst();
2079     QCOMPARE(curIndex.row(), 1);
2080     QCOMPARE(curIndex.column(), 1);
2081 
2082     // previous, last matching cell reached
2083     searchReplaceWidget->findPrevious(true);
2084     indexes = view->selectionModel()->selectedIndexes();
2085     QCOMPARE(indexes.count(), 1);
2086     curIndex = indexes.constFirst();
2087     QCOMPARE(curIndex.row(), 1);
2088     QCOMPARE(curIndex.column(), 1);
2089 }
2090 
2091 void SpreadsheetTest::testSearchFindAll() {
2092     Project project;
2093     auto* sheet = createSearchReplaceSpreadsheet();
2094     project.addChild(sheet);
2095 
2096     // initialize the search&replace widget
2097     auto* searchReplaceWidget = new SearchReplaceWidget(sheet, nullptr);
2098     searchReplaceWidget->setReplaceEnabled(true);
2099     searchReplaceWidget->setDataType(SearchReplaceWidget::DataType::Text);
2100     searchReplaceWidget->setOrder(SearchReplaceWidget::Order::ColumnMajor);
2101     searchReplaceWidget->setTextOperator(SearchReplaceWidget::OperatorText::StartsWith);
2102 
2103     // search for cells starting with "B" and highlight them ("find")
2104     searchReplaceWidget->setInitialPattern(AbstractColumn::ColumnMode::Text, QLatin1String("B"));
2105     searchReplaceWidget->findAll();
2106 
2107     // checks
2108     auto* view = static_cast<SpreadsheetView*>(sheet->view());
2109     auto indexes = view->selectionModel()->selectedIndexes();
2110     QCOMPARE(indexes.size(), 2);
2111     QCOMPARE(indexes.at(0).row(), 1);
2112     QCOMPARE(indexes.at(0).column(), 0);
2113     QCOMPARE(indexes.at(1).row(), 0);
2114     QCOMPARE(indexes.at(1).column(), 2);
2115 }
2116 
2117 void SpreadsheetTest::testSearchReplaceAll() {
2118     Project project;
2119     auto* sheet = createSearchReplaceSpreadsheet();
2120     project.addChild(sheet);
2121 
2122     // initialize the search&replace widget
2123     auto* searchReplaceWidget = new SearchReplaceWidget(sheet, nullptr);
2124     searchReplaceWidget->setReplaceEnabled(true);
2125     searchReplaceWidget->setDataType(SearchReplaceWidget::DataType::Text);
2126     searchReplaceWidget->setOrder(SearchReplaceWidget::Order::ColumnMajor);
2127     searchReplaceWidget->setTextOperator(SearchReplaceWidget::OperatorText::RegEx);
2128 
2129     // search for "A" or "C" and replace with "test"
2130     searchReplaceWidget->setInitialPattern(AbstractColumn::ColumnMode::Text, QLatin1String("[A.C]"));
2131     searchReplaceWidget->setReplaceText(QLatin1String("test"));
2132     searchReplaceWidget->replaceAll();
2133 
2134     // checks
2135     const auto& columns = sheet->children<Column>();
2136     QCOMPARE(columns.at(0)->textAt(0), QLatin1String("test"));
2137     QCOMPARE(columns.at(0)->textAt(2), QLatin1String("test"));
2138     QCOMPARE(columns.at(2)->textAt(1), QLatin1String("test"));
2139     QCOMPARE(columns.at(2)->textAt(2), QLatin1String("test"));
2140 }
2141 
2142 // **********************************************************
2143 // ********************** size changes  *********************
2144 // **********************************************************
2145 void SpreadsheetTest::testInsertRows() {
2146     Project project;
2147     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2148     project.addChild(sheet);
2149 
2150     auto* model = new SpreadsheetModel(sheet);
2151     int rowsAboutToBeInsertedCounter = 0;
2152     connect(model, &SpreadsheetModel::rowsAboutToBeInserted, [&rowsAboutToBeInsertedCounter]() {
2153         rowsAboutToBeInsertedCounter++;
2154     });
2155     int rowsInsertedCounter = 0;
2156     connect(model, &SpreadsheetModel::rowsInserted, [&rowsInsertedCounter]() {
2157         rowsInsertedCounter++;
2158     });
2159     int rowsAboutToBeRemovedCounter = 0;
2160     connect(model, &SpreadsheetModel::rowsAboutToBeRemoved, [&rowsAboutToBeRemovedCounter]() {
2161         rowsAboutToBeRemovedCounter++;
2162     });
2163     int rowsRemovedCounter = 0;
2164     connect(model, &SpreadsheetModel::rowsRemoved, [&rowsRemovedCounter]() {
2165         rowsRemovedCounter++;
2166     });
2167 
2168     QCOMPARE(sheet->rowCount(), 100);
2169     QCOMPARE(model->rowCount(), 100);
2170     sheet->setRowCount(101); // No crash shall happen
2171     QCOMPARE(sheet->rowCount(), 101);
2172     QCOMPARE(model->rowCount(), 101);
2173 
2174     sheet->undoStack()->undo();
2175     QCOMPARE(sheet->rowCount(), 100);
2176     QCOMPARE(model->rowCount(), 100);
2177     sheet->undoStack()->redo();
2178     QCOMPARE(sheet->rowCount(), 101);
2179     QCOMPARE(model->rowCount(), 101);
2180 
2181     QCOMPARE(rowsAboutToBeInsertedCounter, 2); // set and redo()
2182     QCOMPARE(rowsInsertedCounter, 2); // set and redo()
2183     QCOMPARE(rowsAboutToBeRemovedCounter, 1); // undo()
2184     QCOMPARE(rowsRemovedCounter, 1); // undo()
2185 }
2186 
2187 void SpreadsheetTest::testRemoveRows() {
2188     Project project;
2189     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2190     project.addChild(sheet);
2191 
2192     auto* model = new SpreadsheetModel(sheet);
2193     int rowsAboutToBeInsertedCounter = 0;
2194     connect(model, &SpreadsheetModel::rowsAboutToBeInserted, [&rowsAboutToBeInsertedCounter]() {
2195         rowsAboutToBeInsertedCounter++;
2196     });
2197     int rowsInsertedCounter = 0;
2198     connect(model, &SpreadsheetModel::rowsInserted, [&rowsInsertedCounter]() {
2199         rowsInsertedCounter++;
2200     });
2201     int rowsAboutToBeRemovedCounter = 0;
2202     connect(model, &SpreadsheetModel::rowsAboutToBeRemoved, [&rowsAboutToBeRemovedCounter]() {
2203         rowsAboutToBeRemovedCounter++;
2204     });
2205     int rowsRemovedCounter = 0;
2206     connect(model, &SpreadsheetModel::rowsRemoved, [&rowsRemovedCounter]() {
2207         rowsRemovedCounter++;
2208     });
2209 
2210     QCOMPARE(sheet->rowCount(), 100);
2211     QCOMPARE(model->rowCount(), 100);
2212     sheet->setRowCount(10); // No crash shall happen
2213     QCOMPARE(sheet->rowCount(), 10);
2214     QCOMPARE(model->rowCount(), 10);
2215 
2216     sheet->undoStack()->undo();
2217     QCOMPARE(sheet->rowCount(), 100);
2218     QCOMPARE(model->rowCount(), 100);
2219     sheet->undoStack()->redo();
2220     QCOMPARE(sheet->rowCount(), 10);
2221     QCOMPARE(model->rowCount(), 10);
2222 
2223     QCOMPARE(rowsAboutToBeInsertedCounter, 1); // undo
2224     QCOMPARE(rowsInsertedCounter, 1); // undo
2225     QCOMPARE(rowsAboutToBeRemovedCounter, 2); // set and redo()
2226     QCOMPARE(rowsRemovedCounter, 2); // set and redo()
2227 }
2228 
2229 void SpreadsheetTest::testInsertRowsBegin() {
2230     Project project;
2231     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2232     project.addChild(sheet);
2233 
2234     auto* model = new SpreadsheetModel(sheet);
2235     int rowsAboutToBeInsertedCounter = 0;
2236     connect(model, &SpreadsheetModel::rowsAboutToBeInserted, [&rowsAboutToBeInsertedCounter]() {
2237         rowsAboutToBeInsertedCounter++;
2238     });
2239     int rowsInsertedCounter = 0;
2240     connect(model, &SpreadsheetModel::rowsInserted, [&rowsInsertedCounter]() {
2241         rowsInsertedCounter++;
2242     });
2243     int rowsAboutToBeRemovedCounter = 0;
2244     connect(model, &SpreadsheetModel::rowsAboutToBeRemoved, [&rowsAboutToBeRemovedCounter]() {
2245         rowsAboutToBeRemovedCounter++;
2246     });
2247     int rowsRemovedCounter = 0;
2248     connect(model, &SpreadsheetModel::rowsRemoved, [&rowsRemovedCounter]() {
2249         rowsRemovedCounter++;
2250     });
2251 
2252     QCOMPARE(sheet->rowCount(), 100);
2253     QCOMPARE(model->rowCount(), 100);
2254     sheet->insertRows(0, 1); // No crash shall happen
2255     QCOMPARE(sheet->rowCount(), 101);
2256     QCOMPARE(model->rowCount(), 101);
2257 
2258     sheet->undoStack()->undo();
2259     QCOMPARE(sheet->rowCount(), 100);
2260     QCOMPARE(model->rowCount(), 100);
2261     sheet->undoStack()->redo();
2262     QCOMPARE(sheet->rowCount(), 101);
2263     QCOMPARE(model->rowCount(), 101);
2264 
2265     QCOMPARE(rowsAboutToBeInsertedCounter, 2); // set and redo()
2266     QCOMPARE(rowsInsertedCounter, 2); // set and redo()
2267     QCOMPARE(rowsAboutToBeRemovedCounter, 1); // undo()
2268     QCOMPARE(rowsRemovedCounter, 1); // undo()
2269 }
2270 void SpreadsheetTest::testRemoveRowsBegin() {
2271     Project project;
2272     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2273     project.addChild(sheet);
2274 
2275     auto* model = new SpreadsheetModel(sheet);
2276     int rowsAboutToBeInsertedCounter = 0;
2277     connect(model, &SpreadsheetModel::rowsAboutToBeInserted, [&rowsAboutToBeInsertedCounter]() {
2278         rowsAboutToBeInsertedCounter++;
2279     });
2280     int rowsInsertedCounter = 0;
2281     connect(model, &SpreadsheetModel::rowsInserted, [&rowsInsertedCounter]() {
2282         rowsInsertedCounter++;
2283     });
2284     int rowsAboutToBeRemovedCounter = 0;
2285     connect(model, &SpreadsheetModel::rowsAboutToBeRemoved, [&rowsAboutToBeRemovedCounter]() {
2286         rowsAboutToBeRemovedCounter++;
2287     });
2288     int rowsRemovedCounter = 0;
2289     connect(model, &SpreadsheetModel::rowsRemoved, [&rowsRemovedCounter]() {
2290         rowsRemovedCounter++;
2291     });
2292 
2293     QCOMPARE(sheet->rowCount(), 100);
2294     QCOMPARE(model->rowCount(), 100);
2295     sheet->removeRows(0, 1);
2296     QCOMPARE(sheet->rowCount(), 99);
2297     QCOMPARE(model->rowCount(), 99);
2298 
2299     sheet->undoStack()->undo();
2300     QCOMPARE(sheet->rowCount(), 100);
2301     QCOMPARE(model->rowCount(), 100);
2302     sheet->undoStack()->redo();
2303     QCOMPARE(sheet->rowCount(), 99);
2304     QCOMPARE(model->rowCount(), 99);
2305 
2306     QCOMPARE(rowsAboutToBeInsertedCounter, 1); // undo
2307     QCOMPARE(rowsInsertedCounter, 1); // undo
2308     QCOMPARE(rowsAboutToBeRemovedCounter, 2); // set and redo()
2309     QCOMPARE(rowsRemovedCounter, 2); // set and redo()
2310 }
2311 
2312 void SpreadsheetTest::testInsertColumns() {
2313     Project project;
2314     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2315     project.addChild(sheet);
2316 
2317     auto* model = new SpreadsheetModel(sheet);
2318 
2319     int columnsAboutToBeInsertedCounter = 0;
2320     connect(model, &SpreadsheetModel::columnsAboutToBeInserted, [&columnsAboutToBeInsertedCounter]() {
2321         columnsAboutToBeInsertedCounter++;
2322     });
2323     int columnsInsertedCounter = 0;
2324     connect(model, &SpreadsheetModel::columnsInserted, [&columnsInsertedCounter]() {
2325         columnsInsertedCounter++;
2326     });
2327     int columnsAboutToBeRemovedCounter = 0;
2328     connect(model, &SpreadsheetModel::columnsAboutToBeRemoved, [&columnsAboutToBeRemovedCounter]() {
2329         columnsAboutToBeRemovedCounter++;
2330     });
2331     int columnsRemovedCounter = 0;
2332     connect(model, &SpreadsheetModel::columnsRemoved, [&columnsRemovedCounter]() {
2333         columnsRemovedCounter++;
2334     });
2335 
2336     QCOMPARE(sheet->columnCount(), 2);
2337     QCOMPARE(model->columnCount(), 2);
2338     sheet->setColumnCount(5); // No crash shall happen
2339     QCOMPARE(sheet->columnCount(), 5);
2340     QCOMPARE(model->columnCount(), 5);
2341 
2342     sheet->undoStack()->undo();
2343     QCOMPARE(sheet->columnCount(), 2);
2344     QCOMPARE(model->columnCount(), 2);
2345     sheet->undoStack()->redo();
2346     QCOMPARE(sheet->columnCount(), 5);
2347     QCOMPARE(model->columnCount(), 5);
2348 
2349     QCOMPARE(columnsAboutToBeInsertedCounter, 2); // set and redo()
2350     QCOMPARE(columnsInsertedCounter, 2); // set and redo()
2351     QCOMPARE(columnsRemovedCounter, 1); // undo()
2352     QCOMPARE(columnsAboutToBeRemovedCounter, 1); // undo()
2353 }
2354 
2355 void SpreadsheetTest::testRemoveColumns() {
2356     Project project;
2357     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2358     project.addChild(sheet);
2359 
2360     auto* model = new SpreadsheetModel(sheet);
2361 
2362     int columnsAboutToBeInsertedCounter = 0;
2363     connect(model, &SpreadsheetModel::columnsAboutToBeInserted, [&columnsAboutToBeInsertedCounter]() {
2364         columnsAboutToBeInsertedCounter++;
2365     });
2366     int columnsInsertedCounter = 0;
2367     connect(model, &SpreadsheetModel::columnsInserted, [&columnsInsertedCounter]() {
2368         columnsInsertedCounter++;
2369     });
2370     int columnsAboutToBeRemovedCounter = 0;
2371     connect(model, &SpreadsheetModel::columnsAboutToBeRemoved, [&columnsAboutToBeRemovedCounter]() {
2372         columnsAboutToBeRemovedCounter++;
2373     });
2374     int columnsRemovedCounter = 0;
2375     connect(model, &SpreadsheetModel::columnsRemoved, [&columnsRemovedCounter]() {
2376         columnsRemovedCounter++;
2377     });
2378 
2379     QCOMPARE(sheet->columnCount(), 2);
2380     QCOMPARE(model->columnCount(), 2);
2381     sheet->setColumnCount(1); // No crash shall happen
2382     QCOMPARE(sheet->columnCount(), 1);
2383     QCOMPARE(model->columnCount(), 1);
2384 
2385     sheet->undoStack()->undo();
2386     QCOMPARE(sheet->columnCount(), 2);
2387     QCOMPARE(model->columnCount(), 2);
2388     sheet->undoStack()->redo();
2389     QCOMPARE(sheet->columnCount(), 1);
2390     QCOMPARE(model->columnCount(), 1);
2391 
2392     QCOMPARE(columnsAboutToBeInsertedCounter, 1); // undo()
2393     QCOMPARE(columnsInsertedCounter, 1); // undo()
2394     QCOMPARE(columnsRemovedCounter, 2); // set and redo()
2395     QCOMPARE(columnsAboutToBeRemovedCounter, 2); // set and redo()
2396 }
2397 
2398 /*!
2399  * \brief testInsertRowsSuppressUpdate
2400  * It shall not crash
2401  * Testing if in the model begin and end are used properly
2402  */
2403 void SpreadsheetTest::testInsertRowsSuppressUpdate() {
2404     Project project;
2405     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2406     project.addChild(sheet);
2407 
2408     auto* model = new SpreadsheetModel(sheet);
2409 
2410     int rowsAboutToBeInsertedCounter = 0;
2411     connect(model, &SpreadsheetModel::rowsAboutToBeInserted, [&rowsAboutToBeInsertedCounter]() {
2412         rowsAboutToBeInsertedCounter++;
2413     });
2414     int rowsInsertedCounter = 0;
2415     connect(model, &SpreadsheetModel::rowsInserted, [&rowsInsertedCounter]() {
2416         rowsInsertedCounter++;
2417     });
2418     int rowsAboutToBeRemovedCounter = 0;
2419     connect(model, &SpreadsheetModel::rowsAboutToBeRemoved, [&rowsAboutToBeRemovedCounter]() {
2420         rowsAboutToBeRemovedCounter++;
2421     });
2422     int rowsRemovedCounter = 0;
2423     connect(model, &SpreadsheetModel::rowsRemoved, [&rowsRemovedCounter]() {
2424         rowsRemovedCounter++;
2425     });
2426 
2427     int modelResetCounter = 0;
2428     connect(model, &SpreadsheetModel::modelReset, [&modelResetCounter]() {
2429         modelResetCounter++;
2430     });
2431     int modelAboutToResetCounter = 0;
2432     connect(model, &SpreadsheetModel::modelAboutToBeReset, [&modelAboutToResetCounter]() {
2433         modelAboutToResetCounter++;
2434     });
2435 
2436     model->suppressSignals(true);
2437 
2438     QCOMPARE(sheet->rowCount(), 100);
2439     sheet->setRowCount(101); // No crash shall happen
2440     QCOMPARE(sheet->rowCount(), 101);
2441 
2442     sheet->undoStack()->undo();
2443     QCOMPARE(sheet->rowCount(), 100);
2444     sheet->undoStack()->redo();
2445     QCOMPARE(sheet->rowCount(), 101);
2446 
2447     model->suppressSignals(false);
2448 
2449     QCOMPARE(rowsAboutToBeInsertedCounter, 0);
2450     QCOMPARE(rowsInsertedCounter, 0);
2451     QCOMPARE(rowsAboutToBeRemovedCounter, 0);
2452     QCOMPARE(rowsRemovedCounter, 0);
2453     QCOMPARE(modelResetCounter, 1);
2454     QCOMPARE(modelAboutToResetCounter, 1);
2455 }
2456 
2457 /*!
2458  * \brief testInsertColumnsSuppressUpdate
2459  * It shall not crash
2460  * Testing if in the model begin and end are used properly
2461  */
2462 void SpreadsheetTest::testInsertColumnsSuppressUpdate() {
2463     Project project;
2464     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2465     project.addChild(sheet);
2466 
2467     auto* model = new SpreadsheetModel(sheet);
2468 
2469     int columnsAboutToBeInsertedCounter = 0;
2470     connect(model, &SpreadsheetModel::columnsAboutToBeInserted, [&columnsAboutToBeInsertedCounter]() {
2471         columnsAboutToBeInsertedCounter++;
2472     });
2473     int columnsInsertedCounter = 0;
2474     connect(model, &SpreadsheetModel::columnsInserted, [&columnsInsertedCounter]() {
2475         columnsInsertedCounter++;
2476     });
2477     int columnsAboutToBeRemovedCounter = 0;
2478     connect(model, &SpreadsheetModel::columnsAboutToBeRemoved, [&columnsAboutToBeRemovedCounter]() {
2479         columnsAboutToBeRemovedCounter++;
2480     });
2481     int columnsRemovedCounter = 0;
2482     connect(model, &SpreadsheetModel::columnsRemoved, [&columnsRemovedCounter]() {
2483         columnsRemovedCounter++;
2484     });
2485 
2486     int modelResetCounter = 0;
2487     connect(model, &SpreadsheetModel::modelReset, [&modelResetCounter]() {
2488         modelResetCounter++;
2489     });
2490     int modelAboutToResetCounter = 0;
2491     connect(model, &SpreadsheetModel::modelAboutToBeReset, [&modelAboutToResetCounter]() {
2492         modelAboutToResetCounter++;
2493     });
2494 
2495     model->suppressSignals(true);
2496 
2497     QCOMPARE(sheet->columnCount(), 2);
2498     sheet->setColumnCount(5); // No crash shall happen
2499     QCOMPARE(sheet->columnCount(), 5);
2500 
2501     sheet->undoStack()->undo();
2502     QCOMPARE(sheet->columnCount(), 2);
2503     sheet->undoStack()->redo();
2504     QCOMPARE(sheet->columnCount(), 5);
2505 
2506     model->suppressSignals(false);
2507 
2508     QCOMPARE(columnsAboutToBeInsertedCounter, 0);
2509     QCOMPARE(columnsInsertedCounter, 0);
2510     QCOMPARE(columnsRemovedCounter, 0);
2511     QCOMPARE(columnsAboutToBeRemovedCounter, 0);
2512     QCOMPARE(modelResetCounter, 1);
2513     QCOMPARE(modelAboutToResetCounter, 1);
2514 }
2515 
2516 void SpreadsheetTest::testLinkSpreadsheetsUndoRedo() {
2517 #ifdef __FreeBSD__
2518     return;
2519 #endif
2520     Project project;
2521     auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
2522     project.addChild(sheetData);
2523     sheetData->setColumnCount(3);
2524     sheetData->setRowCount(10);
2525 
2526     auto* sheetData2 = new Spreadsheet(QStringLiteral("data2"), false);
2527     project.addChild(sheetData2);
2528     sheetData2->setColumnCount(3);
2529     sheetData2->setRowCount(100);
2530 
2531     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
2532     project.addChild(sheetCalculations);
2533     sheetCalculations->setColumnCount(3);
2534     sheetCalculations->setRowCount(2);
2535 
2536     SpreadsheetDock dock(nullptr);
2537     dock.setSpreadsheets({sheetCalculations});
2538 
2539     QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), false);
2540     QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), false);
2541     QCOMPARE(dock.ui.sbRowCount->isEnabled(), true);
2542     QCOMPARE(dock.m_spreadsheet->linking(), false);
2543 
2544     dock.ui.cbLinkingEnabled->toggled(true);
2545 
2546     // QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), true); // does not work here. Don't know why
2547     // QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), true);
2548     // QCOMPARE(dock.ui.sbRowCount->isEnabled(), false);
2549     QCOMPARE(sheetCalculations->linking(), true);
2550     QCOMPARE(sheetCalculations->linkedSpreadsheet(), nullptr);
2551     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), QLatin1String());
2552     QCOMPARE(sheetCalculations->rowCount(), 2);
2553 
2554     const auto index = dock.m_aspectTreeModel->modelIndexOfAspect(sheetData);
2555     QCOMPARE(index.isValid(), true);
2556     // dock.ui.cbLinkedSpreadsheet->setCurrentModelIndex(index); // Does not trigger the slot
2557     sheetCalculations->setLinkedSpreadsheet(sheetData);
2558 
2559     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2560     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2561     QCOMPARE(sheetCalculations->rowCount(), 10);
2562 
2563     sheetCalculations->setLinkedSpreadsheet(sheetData2);
2564 
2565     QCOMPARE(sheetCalculations->linking(), true);
2566     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData2);
2567     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData2->path());
2568     QCOMPARE(sheetCalculations->rowCount(), 100);
2569 
2570     sheetCalculations->undoStack()->undo();
2571 
2572     QCOMPARE(sheetCalculations->linking(), true);
2573     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2574     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2575     QCOMPARE(sheetCalculations->rowCount(), 10);
2576 
2577     sheetCalculations->undoStack()->redo();
2578 
2579     QCOMPARE(sheetCalculations->linking(), true);
2580     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData2);
2581     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData2->path());
2582     QCOMPARE(sheetCalculations->rowCount(), 100);
2583 
2584     sheetCalculations->undoStack()->undo(); // first undo
2585     QCOMPARE(sheetCalculations->linking(), true);
2586     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2587     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2588     QCOMPARE(sheetCalculations->rowCount(), 10);
2589 
2590     sheetCalculations->undoStack()->undo();
2591 
2592     QCOMPARE(sheetCalculations->linking(), true);
2593     QCOMPARE(sheetCalculations->linkedSpreadsheet(), nullptr); // No linked spreadsheet anymore
2594     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), QLatin1String());
2595     QCOMPARE(sheetCalculations->rowCount(), 2); // Go back to original row count
2596 
2597     sheetCalculations->undoStack()->undo();
2598     QCOMPARE(sheetCalculations->linking(), false);
2599     QCOMPARE(sheetCalculations->linkedSpreadsheet(), nullptr);
2600     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), QLatin1String());
2601     QCOMPARE(sheetCalculations->rowCount(), 2);
2602 }
2603 
2604 void SpreadsheetTest::testLinkSpreadsheetDeleteAdd() {
2605 #ifdef __FreeBSD__
2606     return;
2607 #endif
2608     Project project;
2609     auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
2610     project.addChild(sheetData);
2611     sheetData->setColumnCount(3);
2612     sheetData->setRowCount(10);
2613 
2614     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
2615     project.addChild(sheetCalculations);
2616     sheetCalculations->setColumnCount(3);
2617     sheetCalculations->setRowCount(2);
2618 
2619     SpreadsheetDock dock(nullptr);
2620     dock.setSpreadsheets({sheetCalculations});
2621 
2622     QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), false);
2623     QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), false);
2624     QCOMPARE(dock.ui.sbRowCount->isEnabled(), true);
2625     QCOMPARE(sheetCalculations->linking(), false);
2626 
2627     Q_EMIT dock.ui.cbLinkingEnabled->toggled(true);
2628 
2629     sheetCalculations->setLinkedSpreadsheet(sheetData);
2630 
2631     QCOMPARE(sheetCalculations->linking(), true);
2632     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2633     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2634     QCOMPARE(sheetCalculations->rowCount(), 10);
2635 
2636     sheetData->remove();
2637     sheetData->setRowCount(100);
2638 
2639     QCOMPARE(sheetCalculations->linking(), true);
2640     QCOMPARE(sheetCalculations->linkedSpreadsheet(), nullptr);
2641     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), QStringLiteral("Project/data"));
2642     QCOMPARE(sheetCalculations->rowCount(), 10); // does not change
2643 
2644     auto* sheetDataNew = new Spreadsheet(QStringLiteral("data"), false);
2645     sheetDataNew->setColumnCount(3);
2646     sheetDataNew->setRowCount(12);
2647     project.addChild(sheetDataNew);
2648 
2649     QCOMPARE(sheetCalculations->linking(), true);
2650     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetDataNew);
2651     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetDataNew->path());
2652     QCOMPARE(sheetCalculations->rowCount(), 12);
2653 }
2654 
2655 void SpreadsheetTest::testLinkSpreadsheetAddRow() {
2656 #ifdef __FreeBSD__
2657     return;
2658 #endif
2659     Project project;
2660     auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
2661     project.addChild(sheetData);
2662     sheetData->setColumnCount(3);
2663     sheetData->setRowCount(10);
2664 
2665     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
2666     project.addChild(sheetCalculations);
2667     sheetCalculations->setColumnCount(3);
2668     sheetCalculations->setRowCount(2);
2669 
2670     SpreadsheetDock dock(nullptr);
2671     dock.setSpreadsheets({sheetCalculations});
2672 
2673     QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), false);
2674     QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), false);
2675     QCOMPARE(dock.ui.sbRowCount->isEnabled(), true);
2676     QCOMPARE(sheetCalculations->linking(), false);
2677 
2678     Q_EMIT dock.ui.cbLinkingEnabled->toggled(true);
2679 
2680     sheetCalculations->setLinkedSpreadsheet(sheetData);
2681 
2682     QCOMPARE(sheetCalculations->linking(), true);
2683     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2684     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2685     QCOMPARE(sheetCalculations->rowCount(), 10);
2686 
2687     new SpreadsheetModel(sheetData); // otherwise emitRowCountChanged will not be called
2688     sheetData->setRowCount(13);
2689 
2690     QCOMPARE(sheetCalculations->linking(), true);
2691     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2692     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2693     QCOMPARE(sheetCalculations->rowCount(), 13);
2694 }
2695 
2696 void SpreadsheetTest::testLinkSpreadsheetRemoveRow() {
2697 #ifdef __FreeBSD__
2698     return;
2699 #endif
2700     Project project;
2701     auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
2702     project.addChild(sheetData);
2703     sheetData->setColumnCount(3);
2704     sheetData->setRowCount(10);
2705 
2706     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
2707     project.addChild(sheetCalculations);
2708     sheetCalculations->setColumnCount(3);
2709     sheetCalculations->setRowCount(2);
2710 
2711     SpreadsheetDock dock(nullptr);
2712     dock.setSpreadsheets({sheetCalculations});
2713 
2714     QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), false);
2715     QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), false);
2716     QCOMPARE(dock.ui.sbRowCount->isEnabled(), true);
2717     QCOMPARE(sheetCalculations->linking(), false);
2718 
2719     dock.ui.cbLinkingEnabled->toggled(true);
2720 
2721     sheetCalculations->setLinkedSpreadsheet(sheetData);
2722 
2723     QCOMPARE(sheetCalculations->linking(), true);
2724     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2725     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2726     QCOMPARE(sheetCalculations->rowCount(), 10);
2727 
2728     new SpreadsheetModel(sheetData); // otherwise emitRowCountChanged will not be called
2729     sheetData->setRowCount(7);
2730 
2731     QCOMPARE(sheetCalculations->linking(), true);
2732     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2733     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2734     QCOMPARE(sheetCalculations->rowCount(), 7);
2735 }
2736 
2737 void SpreadsheetTest::testLinkSpreadsheetRecalculate() {
2738 #ifdef __FreeBSD__
2739     return;
2740 #endif
2741     Project project;
2742     auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
2743     project.addChild(sheetData);
2744     sheetData->setColumnCount(2);
2745     sheetData->setRowCount(10);
2746     auto* sheetDataColumn0 = sheetData->child<Column>(0);
2747     sheetDataColumn0->replaceValues(0, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
2748     QVERIFY(sheetDataColumn0);
2749     auto* sheetDataColumn1 = sheetData->child<Column>(1);
2750     QVERIFY(sheetDataColumn1);
2751     sheetDataColumn1->replaceValues(0, {1, 2, 1, 2, 1, 2, 1, 2, 1, 3});
2752 
2753     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
2754     project.addChild(sheetCalculations);
2755     sheetCalculations->setColumnCount(1);
2756     sheetCalculations->setRowCount(2);
2757     auto* sheetCalculationsColumn0 = sheetCalculations->child<Column>(0);
2758     QVERIFY(sheetCalculationsColumn0);
2759     sheetCalculationsColumn0->setFormula(QStringLiteral("x + y"), {QStringLiteral("x"), QStringLiteral("y")}, {sheetDataColumn0, sheetDataColumn1}, true);
2760     sheetCalculationsColumn0->updateFormula();
2761 
2762     {
2763         QVector<double> ref{2, 4, 4, 6, 6, 8, 8, 10, 10, 13};
2764         QCOMPARE(sheetCalculationsColumn0->rowCount(), 10); // currently the update() triggers a resize
2765         for (int i = 0; i < 10; i++)
2766             VALUES_EQUAL(sheetCalculationsColumn0->doubleAt(i), ref.at(i));
2767     }
2768     sheetCalculations->setLinking(true);
2769     sheetCalculations->setLinkedSpreadsheet(sheetData);
2770 
2771     QCOMPARE(sheetCalculations->linking(), true);
2772     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2773     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2774     QCOMPARE(sheetCalculations->rowCount(), 10);
2775 
2776     new SpreadsheetModel(sheetData); // otherwise emitRowCountChanged will not be called
2777     sheetData->setRowCount(7);
2778     sheetDataColumn0->replaceValues(0, {3, 4, 6, 2, 1, 8, 5});
2779     QCOMPARE(sheetDataColumn0->rowCount(), 7);
2780 
2781     {
2782         QVector<double> ref{4, 6, 7, 4, 2, 10, 6};
2783         QCOMPARE(sheetCalculationsColumn0->rowCount(), ref.count());
2784         for (int i = 0; i < ref.count(); i++) {
2785             qDebug() << i;
2786             VALUES_EQUAL(sheetCalculationsColumn0->doubleAt(i), ref.at(i));
2787         }
2788     }
2789 }
2790 
2791 void SpreadsheetTest::testLinkSpreadsheetSaveLoad() {
2792 #ifdef __FreeBSD__
2793     return;
2794 #endif
2795     QString savePath;
2796     {
2797         Project project;
2798         auto model = new AspectTreeModel(&project, this);
2799         ProjectExplorer pe; // Needed otherwise the state key is missing in the file and then no restorePointers will be called
2800         pe.setProject(&project);
2801         pe.setModel(model);
2802         auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
2803         project.addChild(sheetData);
2804         sheetData->setColumnCount(3);
2805         sheetData->setRowCount(10);
2806 
2807         auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
2808         project.addChild(sheetCalculations);
2809         sheetCalculations->setColumnCount(3);
2810         sheetCalculations->setRowCount(2);
2811 
2812         SpreadsheetDock dock(nullptr);
2813         dock.setSpreadsheets({sheetCalculations});
2814 
2815         QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), false);
2816         QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), false);
2817         QCOMPARE(dock.ui.sbRowCount->isEnabled(), true);
2818         QCOMPARE(sheetCalculations->linking(), false);
2819 
2820         Q_EMIT dock.ui.cbLinkingEnabled->toggled(true);
2821 
2822         sheetCalculations->setLinkedSpreadsheet(sheetData);
2823 
2824         QCOMPARE(sheetCalculations->linking(), true);
2825         QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2826         QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2827         QCOMPARE(sheetCalculations->rowCount(), 10);
2828 
2829         SAVE_PROJECT("testLinkSpreadsheetSaveLoad")
2830     }
2831 
2832     {
2833         Project project;
2834         QCOMPARE(project.load(savePath), true);
2835 
2836         auto sheetData = project.child<Spreadsheet>(0);
2837         QVERIFY(sheetData);
2838         auto sheetCalculations = project.child<Spreadsheet>(1);
2839         QVERIFY(sheetCalculations);
2840 
2841         QCOMPARE(sheetCalculations->linking(), true);
2842         QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2843         QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2844         QCOMPARE(sheetCalculations->rowCount(), 10);
2845 
2846         new SpreadsheetModel(sheetData); // otherwise emitRowCountChanged will not be called
2847         sheetData->setRowCount(11); // Changing shall also update sheetCalculations also after loading
2848 
2849         QCOMPARE(sheetCalculations->linking(), true);
2850         QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2851         QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2852         QCOMPARE(sheetCalculations->rowCount(), 11);
2853     }
2854 }
2855 
2856 // **********************************************************
2857 // **************** statistics spreadsheet  *****************
2858 // **********************************************************
2859 /*!
2860  * toggle on and off the statistics spreadsheet and check its presence
2861  */
2862 void SpreadsheetTest::testStatisticsSpreadsheetToggle() {
2863     Project project;
2864     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2865     project.addChild(sheet);
2866 
2867     // initial
2868     auto children = sheet->children<StatisticsSpreadsheet>();
2869     QCOMPARE(children.size(), 0);
2870 
2871     // toggle on
2872     sheet->toggleStatisticsSpreadsheet(true);
2873     children = sheet->children<StatisticsSpreadsheet>();
2874     QCOMPARE(children.size(), 1);
2875 
2876     // toggle off
2877     sheet->toggleStatisticsSpreadsheet(false);
2878     children = sheet->children<StatisticsSpreadsheet>();
2879     QCOMPARE(children.size(), 0);
2880 }
2881 
2882 /*!
2883  * change the statistics metrics and check the presense of the corresponding columns
2884  */
2885 void SpreadsheetTest::testStatisticsSpreadsheetChangeMetrics() {
2886     Project project;
2887     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2888     project.addChild(sheet);
2889 
2890     new SpreadsheetModel(sheet);
2891 
2892     // toggle on the statistics spreadsheet and count the available columns
2893     sheet->toggleStatisticsSpreadsheet(true);
2894     auto* statisticsSpreadsheet = sheet->children<StatisticsSpreadsheet>().constFirst();
2895     auto metrics = statisticsSpreadsheet->metrics();
2896     int colCount = 1; // column "Column Name" is always available
2897     auto it = statisticsSpreadsheet->m_metricNames.constBegin();
2898     while (it != statisticsSpreadsheet->m_metricNames.constEnd()) {
2899         if (metrics.testFlag(it.key()))
2900             ++colCount;
2901         ++it;
2902     }
2903     QCOMPARE(statisticsSpreadsheet->children<Column>().size(), colCount);
2904 
2905     // deactivate Count and check the available columns again
2906     metrics.setFlag(StatisticsSpreadsheet::Metric::Count, false);
2907     statisticsSpreadsheet->setMetrics(metrics);
2908     QCOMPARE(statisticsSpreadsheet->children<Column>().size(), colCount - 1);
2909 }
2910 
2911 /*!
2912  * check the position of the statistics spreadsheet in the list of children of the parent
2913  * spreadsheet, it should always be put at the last position.
2914  */
2915 void SpreadsheetTest::testStatisticsSpreadsheetChildIndex() {
2916     Project project;
2917     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2918     project.addChild(sheet);
2919 
2920     // toggle on the statistics spreadsheet and count the available columns
2921     sheet->toggleStatisticsSpreadsheet(true);
2922     auto* statisticsSpreadsheet = sheet->children<StatisticsSpreadsheet>().constFirst();
2923 
2924     // set the column count and check the position of the statistics spreadsheet
2925     sheet->setColumnCount(2);
2926     QCOMPARE(sheet->indexOfChild<AbstractAspect>(statisticsSpreadsheet), 2);
2927 
2928     // change the column count and check the position again
2929     sheet->setColumnCount(3);
2930     QCOMPARE(sheet->indexOfChild<AbstractAspect>(statisticsSpreadsheet), 3);
2931 
2932     // append one more column and check the position again
2933     sheet->appendColumn();
2934     QCOMPARE(sheet->indexOfChild<AbstractAspect>(statisticsSpreadsheet), 4);
2935 
2936     // check also the position when adding new columns via the actions in the view that also
2937     // take the current selected column into account and have more logic for "append left to"
2938     // and "append right to" - ensure no columns are added after the statistics spreadhseet
2939     auto* view = static_cast<SpreadsheetView*>(sheet->view());
2940 
2941     // insert a new column right to the last selected column, it should be placed in front of the statistics spreadsheet
2942     view->selectColumn(3); // select the last 4th column
2943     view->insertColumnsRight(1);
2944     QCOMPARE(sheet->indexOfChild<AbstractAspect>(statisticsSpreadsheet), 5);
2945 }
2946 
2947 /*!
2948  * check the position of the statistics spreadsheet in the list of children of the parent
2949  * spreadsheet after an undo and redo of a column appen, it should always be put at the last position,
2950  * and also check  the handling of added and removed childrend in the model in the presense of the
2951  * statistics spreadsheet.
2952  */
2953 void SpreadsheetTest::testStatisticsSpreadsheetChildIndexAfterUndoRedo() {
2954     Project project;
2955     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
2956     project.addChild(sheet);
2957 
2958     new SpreadsheetModel(sheet);
2959 
2960     // toggle on the statistics spreadsheet and count the available columns
2961     sheet->toggleStatisticsSpreadsheet(true);
2962     auto* statisticsSpreadsheet = sheet->children<StatisticsSpreadsheet>().constFirst();
2963 
2964     // set the column count , append a column, undo and redo this change and check the position of the statistics spreadsheet
2965     sheet->setColumnCount(2);
2966     sheet->appendColumn();
2967     QCOMPARE(sheet->model()->columnCount(), 3);
2968     QCOMPARE(sheet->indexOfChild<AbstractAspect>(statisticsSpreadsheet), 3);
2969 
2970     project.undoStack()->undo();
2971     QCOMPARE(sheet->model()->columnCount(), 2);
2972     QCOMPARE(sheet->indexOfChild<AbstractAspect>(statisticsSpreadsheet), 2);
2973 
2974     project.undoStack()->redo();
2975     QCOMPARE(sheet->model()->columnCount(), 3);
2976     QCOMPARE(sheet->indexOfChild<AbstractAspect>(statisticsSpreadsheet), 3);
2977 }
2978 
2979 #ifdef HAVE_VECTOR_BLF
2980 // Copied from BLFFilterTest.cpp
2981 namespace {
2982 static const std::string PRIMITIVE_DBC =
2983     R"(VERSION "1.0.0"
2984 
2985 NS_ :
2986 
2987 BS_:
2988 
2989 BU_: DBG DRIVER IO MOTOR SENSOR
2990 
2991 )";
2992 
2993 void createDBCFile(const QString& filename, const std::string& content) {
2994     auto* file = std::fopen(filename.toStdString().c_str(), "w");
2995     QVERIFY(file);
2996     std::fputs(PRIMITIVE_DBC.c_str(), file);
2997     std::fputs(content.c_str(), file);
2998     std::fclose(file);
2999 }
3000 
3001 Vector::BLF::CanMessage2* createCANMessage(uint32_t id, uint64_t timestamp, const std::vector<uint8_t>& data) {
3002     auto* canMessage = new Vector::BLF::CanMessage2();
3003     canMessage->channel = 1;
3004     canMessage->flags = 1; // TX
3005     canMessage->dlc = std::min<uint8_t>(data.size(), 8);
3006     canMessage->id = id;
3007     canMessage->objectTimeStamp = timestamp;
3008     canMessage->objectFlags = Vector::BLF::ObjectHeader::ObjectFlags::TimeOneNans;
3009     if (canMessage->data.size() < canMessage->dlc)
3010         canMessage->data.resize(canMessage->dlc);
3011 
3012     for (int i = 0; i < canMessage->dlc; i++) {
3013         canMessage->data[i] = data.at(i);
3014     }
3015     return canMessage;
3016 }
3017 
3018 void createBLFFile(const QString& filename, QVector<Vector::BLF::CanMessage2*> messages) {
3019     Vector::BLF::File blfFile;
3020     blfFile.open(filename.toStdString().c_str(), std::ios_base::out);
3021     QVERIFY(blfFile.is_open());
3022 
3023     for (auto msg : messages) {
3024         blfFile.write(msg);
3025     }
3026     // Finish creating files
3027     blfFile.close();
3028 }
3029 }
3030 
3031 void SpreadsheetTest::testLinkSpreadSheetImportBLF() {
3032     QTemporaryFile blfFileName(QStringLiteral("XXXXXX.blf"));
3033     QVERIFY(blfFileName.open());
3034     QVector<Vector::BLF::CanMessage2*> messages{
3035         createCANMessage(337, 5, {0, 4, 252, 19, 0, 0, 0, 0}),
3036         createCANMessage(541, 10, {7, 39, 118, 33, 250, 30, 76, 24}), // 99.91, 85.66, 79.3, 22.2
3037         createCANMessage(337, 15, {47, 4, 60, 29, 0, 0, 0, 0}),
3038         createCANMessage(337, 20, {57, 4, 250, 29, 0, 0, 0, 0}),
3039         createCANMessage(541, 25, {7, 39, 118, 33, 250, 30, 76, 24}), // 99.91, 85.66, 79.3, 22.2
3040     }; // time is in nanoseconds
3041     createBLFFile(blfFileName.fileName(), messages);
3042 
3043     QTemporaryFile dbcFile(QStringLiteral("XXXXXX.dbc"));
3044     QVERIFY(dbcFile.open());
3045     const auto dbcContent = R"(BO_ 337 STATUS: 8 Vector__XXX
3046  SG_ Value6 : 27|3@1+ (1,0) [0|7] ""  Vector__XXX
3047  SG_ Value5 : 16|11@1+ (0.1,-102) [-102|102] "%"  Vector__XXX
3048  SG_ Value2 : 8|2@1+ (1,0) [0|2] ""  Vector__XXX
3049  SG_ Value3 : 10|1@1+ (1,0) [0|1] ""  Vector__XXX
3050  SG_ Value7 : 30|2@1+ (1,0) [0|3] ""  Vector__XXX
3051  SG_ Value4 : 11|4@1+ (1,0) [0|3] ""  Vector__XXX
3052  SG_ Value1 : 0|8@1+ (1,0) [0|204] "Km/h"  Vector__XXX"
3053 BO_ 541 MSG2: 8 Vector__XXX
3054  SG_ MSG2Value4 : 48|16@1+ (0.01,-40) [-40|125] "C"  Vector__XXX
3055  SG_ MSG2Value1 : 0|16@1+ (0.01,0) [0|100] "%"  Vector__XXX
3056  SG_ MSG2Value3 : 32|16@1+ (0.01,0) [0|100] "%"  Vector__XXX
3057  SG_ MSG2Value2 : 16|16@1+ (0.01,0) [0|100] "%"  Vector__XXX
3058 )";
3059     createDBCFile(dbcFile.fileName(), dbcContent);
3060 
3061     //------------------------------------------------------------------------------------------
3062     Project project;
3063     const auto spreadsheetName = blfFileName.fileName().replace(QStringLiteral(".blf"), QStringLiteral(""));
3064     auto* sheetData = new Spreadsheet(spreadsheetName, false);
3065     project.addChild(sheetData);
3066     sheetData->setColumnCount(2);
3067     sheetData->setRowCount(10);
3068     auto* sheetDataColumn0 = sheetData->child<Column>(0);
3069     sheetDataColumn0->setName(QStringLiteral("Value6_"));
3070     sheetDataColumn0->replaceValues(0, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
3071     QVERIFY(sheetDataColumn0);
3072     auto* sheetDataColumn1 = sheetData->child<Column>(1);
3073     sheetDataColumn1->setName(QStringLiteral("Value5_%"));
3074     QVERIFY(sheetDataColumn1);
3075     sheetDataColumn1->replaceValues(0, {1, 2, 1, 2, 1, 2, 1, 2, 1, 3});
3076 
3077     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
3078     project.addChild(sheetCalculations);
3079     sheetCalculations->setColumnCount(2);
3080     sheetCalculations->setRowCount(2);
3081 
3082     auto* sheetCalculationsColumn0 = sheetCalculations->child<Column>(0);
3083     QVERIFY(sheetCalculationsColumn0);
3084     sheetCalculationsColumn0->setFormula(QStringLiteral("2*x"), {QStringLiteral("x")}, {sheetDataColumn0}, true);
3085     sheetCalculationsColumn0->updateFormula();
3086 
3087     auto* sheetCalculationsColumn1 = sheetCalculations->child<Column>(1);
3088     QVERIFY(sheetCalculationsColumn1);
3089     sheetCalculationsColumn1->setFormula(QStringLiteral("2*x"), {QStringLiteral("x")}, {sheetDataColumn1}, true);
3090     sheetCalculationsColumn1->updateFormula();
3091 
3092     {
3093         QVector<double> ref{2, 4, 6, 8, 10, 12, 14, 16, 18, 20};
3094         QCOMPARE(sheetCalculationsColumn0->rowCount(), 10);
3095         for (int i = 0; i < 10; i++)
3096             VALUES_EQUAL(sheetCalculationsColumn0->doubleAt(i), ref.at(i));
3097     }
3098 
3099     {
3100         QVector<double> ref{2, 4, 2, 4, 2, 4, 2, 4, 2, 6};
3101         QCOMPARE(sheetCalculationsColumn1->rowCount(), 10);
3102         for (int i = 0; i < 10; i++)
3103             VALUES_EQUAL(sheetCalculationsColumn1->doubleAt(i), ref.at(i));
3104     }
3105 
3106     sheetCalculations->setLinking(true);
3107     sheetCalculations->setLinkedSpreadsheet(sheetData);
3108 
3109     QCOMPARE(sheetCalculations->linking(), true);
3110     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
3111     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
3112     QCOMPARE(sheetCalculations->rowCount(), 10);
3113 
3114     new SpreadsheetModel(sheetData); // otherwise emitRowCountChanged will not be called
3115 
3116     VectorBLFFilter filter;
3117     filter.setConvertTimeToSeconds(true);
3118     filter.setTimeHandlingMode(CANFilter::TimeHandling::ConcatPrevious);
3119     QCOMPARE(filter.isValid(blfFileName.fileName()), true);
3120 
3121     // Valid blf and valid dbc
3122     filter.setDBCFile(dbcFile.fileName());
3123     filter.readDataFromFile(blfFileName.fileName(), sheetData);
3124     QCOMPARE(sheetData->columnCount(), 12);
3125 
3126     QCOMPARE(sheetData->rowCount(), 5);
3127     QCOMPARE(sheetDataColumn0->rowCount(), 5);
3128     QCOMPARE(sheetDataColumn1->rowCount(), 5);
3129 
3130     const auto* sheetDataColumn6 = sheetData->child<Column>(1);
3131     QCOMPARE(sheetDataColumn6->name(), QStringLiteral("Value6_"));
3132     const auto* sheetDataColumn5 = sheetData->child<Column>(2);
3133     QCOMPARE(sheetDataColumn5->name(), QStringLiteral("Value5_%"));
3134 
3135     {
3136         QVector<double> ref{4., 4., 6., 6., 6.};
3137         const auto* sheetCalculationsColumn = sheetCalculations->child<Column>(0);
3138         QCOMPARE(sheetCalculationsColumn->formulaData().at(0).column(), sheetDataColumn6);
3139         QCOMPARE(sheetCalculationsColumn->rowCount(), ref.count());
3140         for (int i = 0; i < ref.count(); i++) {
3141             qDebug() << i;
3142             VALUES_EQUAL(sheetCalculationsColumn->doubleAt(i), ref.at(i));
3143         }
3144     }
3145 
3146     {
3147         QVector<double> ref{0., 0., 64., 102., 102.};
3148         const auto* sheetCalculationsColumn = sheetCalculations->child<Column>(1);
3149         QCOMPARE(sheetCalculationsColumn->formulaData().at(0).column(), sheetDataColumn5);
3150         QCOMPARE(sheetCalculationsColumn->rowCount(), ref.count());
3151         for (int i = 0; i < ref.count(); i++) {
3152             qDebug() << i;
3153             VALUES_EQUAL(sheetCalculationsColumn->doubleAt(i), ref.at(i));
3154         }
3155     }
3156 }
3157 #endif // HAVE_VECTOR_BLF
3158 
3159 /*!
3160  * Columns are named in the correct number order
3161  */
3162 void SpreadsheetTest::testNaming() {
3163     Project project;
3164     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
3165     project.addChild(sheet);
3166 
3167     new SpreadsheetModel(sheet);
3168 
3169     QCOMPARE(sheet->columnCount(), 2);
3170     QCOMPARE(sheet->column(0)->name(), QStringLiteral("1"));
3171     QCOMPARE(sheet->column(1)->name(), QStringLiteral("2"));
3172 
3173     sheet->setColumnCount(10);
3174     QCOMPARE(sheet->columnCount(), 10);
3175     for (int i = 0; i < 10; i++) {
3176         QCOMPARE(sheet->column(i)->name(), QString::number(i + 1));
3177     }
3178 }
3179 
3180 QTEST_MAIN(SpreadsheetTest)