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 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 "commonfrontend/ProjectExplorer.h"
0019 #include "commonfrontend/spreadsheet/SpreadsheetView.h"
0020 #include "kdefrontend/dockwidgets/SpreadsheetDock.h"
0021 #include "kdefrontend/spreadsheet/FlattenColumnsDialog.h"
0022 
0023 #ifdef HAVE_VECTOR_BLF
0024 #include <Vector/BLF.h>
0025 #endif
0026 
0027 #include <QClipboard>
0028 #include <QModelIndex>
0029 #include <QUndoStack>
0030 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
0031 #include <QRandomGenerator>
0032 #endif
0033 
0034 extern "C" {
0035 #include <gsl/gsl_math.h>
0036 }
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 /*!
0409    insert the data at the edge of the spreadsheet and paste the data.
0410    the spreadsheet has to be extended accordingly
0411 */
0412 void SpreadsheetTest::testCopyPasteSizeChange01() {
0413     QLocale::setDefault(QLocale::C); // . as decimal separator
0414     Spreadsheet sheet(QStringLiteral("test"), false);
0415     sheet.setColumnCount(2);
0416     sheet.setRowCount(100);
0417 
0418     const QString str = QStringLiteral(
0419         "1.1 2.2\n"
0420         "3.3 4.4");
0421     QApplication::clipboard()->setText(str);
0422 
0423     SpreadsheetView view(&sheet, false);
0424     view.goToCell(1, 1); // havigate to the edge of the spreadsheet
0425     view.pasteIntoSelection();
0426 
0427     // spreadsheet size
0428     QCOMPARE(sheet.columnCount(), 3);
0429     QCOMPARE(sheet.rowCount(), 100);
0430 
0431     // column modes
0432     QCOMPARE(sheet.column(0)->columnMode(), AbstractColumn::ColumnMode::Double);
0433     QCOMPARE(sheet.column(1)->columnMode(), AbstractColumn::ColumnMode::Double);
0434     QCOMPARE(sheet.column(2)->columnMode(), AbstractColumn::ColumnMode::Double);
0435 
0436     // values
0437     QCOMPARE((bool)std::isnan(sheet.column(0)->valueAt(0)), true);
0438     QCOMPARE((bool)std::isnan(sheet.column(1)->valueAt(0)), true);
0439     QCOMPARE((bool)std::isnan(sheet.column(2)->valueAt(0)), true);
0440 
0441     QCOMPARE((bool)std::isnan(sheet.column(0)->valueAt(1)), true);
0442     QCOMPARE(sheet.column(1)->valueAt(1), 1.1);
0443     QCOMPARE(sheet.column(2)->valueAt(1), 2.2);
0444 
0445     QCOMPARE((bool)std::isnan(sheet.column(0)->valueAt(2)), true);
0446     QCOMPARE(sheet.column(1)->valueAt(2), 3.3);
0447     QCOMPARE(sheet.column(2)->valueAt(2), 4.4);
0448 }
0449 
0450 /////////////////////////////// Sorting tests ////////////////////////////
0451 // single column
0452 
0453 /*
0454  * check sorting single column of double values with NaN ascending
0455  */
0456 void SpreadsheetTest::testSortSingleNumeric1() {
0457     const QVector<double> xData{0.5, -0.2, GSL_NAN, 2.0, -1.0};
0458 
0459     Spreadsheet sheet(QStringLiteral("test"), false);
0460     sheet.setColumnCount(1);
0461     sheet.setRowCount(7);
0462     auto* col = sheet.column(0);
0463     col->replaceValues(0, xData);
0464 
0465     // sort
0466     sheet.sortColumns(nullptr, {col}, true);
0467 
0468     // values
0469     QCOMPARE(col->valueAt(0), -1.0);
0470     QCOMPARE(col->valueAt(1), -0.2);
0471     QCOMPARE(col->valueAt(2), 0.5);
0472     QCOMPARE(col->valueAt(3), 2.0);
0473 }
0474 
0475 /*
0476  * check sorting single column of double values with NaN descending
0477  */
0478 void SpreadsheetTest::testSortSingleNumeric2() {
0479     const QVector<double> xData{0.5, -0.2, GSL_NAN, 2.0, -1.0};
0480 
0481     Spreadsheet sheet(QStringLiteral("test"), false);
0482     sheet.setColumnCount(1);
0483     sheet.setRowCount(7);
0484     auto* col = sheet.column(0);
0485     col->replaceValues(0, xData);
0486 
0487     // sort
0488     sheet.sortColumns(nullptr, {col}, false);
0489 
0490     // values
0491     QCOMPARE(col->valueAt(0), 2.0);
0492     QCOMPARE(col->valueAt(1), 0.5);
0493     QCOMPARE(col->valueAt(2), -0.2);
0494     QCOMPARE(col->valueAt(3), -1.0);
0495 }
0496 
0497 /*
0498  * check sorting single column of integer values with empty entries ascending
0499  */
0500 void SpreadsheetTest::testSortSingleInteger1() {
0501     const QVector<int> xData1{4, 5, 2};
0502     const QVector<int> xData2{3, 6, -1};
0503 
0504     Spreadsheet sheet(QStringLiteral("test"), false);
0505     sheet.setColumnCount(1);
0506     sheet.setRowCount(7);
0507     auto* col = sheet.column(0);
0508     col->setColumnMode(AbstractColumn::ColumnMode::Integer);
0509     col->replaceInteger(0, xData1);
0510     col->replaceInteger(4, xData2);
0511 
0512     // sort
0513     sheet.sortColumns(nullptr, {col}, true);
0514 
0515     // values
0516     QCOMPARE(col->integerAt(0), -1);
0517     QCOMPARE(col->integerAt(1), 0);
0518     QCOMPARE(col->integerAt(2), 2);
0519     QCOMPARE(col->integerAt(3), 3);
0520     QCOMPARE(col->integerAt(4), 4);
0521     QCOMPARE(col->integerAt(5), 5);
0522     QCOMPARE(col->integerAt(6), 6);
0523 }
0524 
0525 /*
0526  * check sorting single column of integer values with empty entries ascending
0527  */
0528 void SpreadsheetTest::testSortSingleInteger2() {
0529     const QVector<int> xData1{4, 5, 2};
0530     const QVector<int> xData2{3, 6, -1};
0531 
0532     Spreadsheet sheet(QStringLiteral("test"), false);
0533     sheet.setColumnCount(1);
0534     sheet.setRowCount(7);
0535     auto* col = sheet.column(0);
0536     col->setColumnMode(AbstractColumn::ColumnMode::Integer);
0537     col->replaceInteger(0, xData1);
0538     col->replaceInteger(4, xData2);
0539 
0540     // sort
0541     sheet.sortColumns(nullptr, {col}, false);
0542 
0543     // values
0544     QCOMPARE(col->integerAt(6), -1);
0545     QCOMPARE(col->integerAt(5), 0);
0546     QCOMPARE(col->integerAt(4), 2);
0547     QCOMPARE(col->integerAt(3), 3);
0548     QCOMPARE(col->integerAt(2), 4);
0549     QCOMPARE(col->integerAt(1), 5);
0550     QCOMPARE(col->integerAt(0), 6);
0551 }
0552 
0553 /*
0554  * check sorting single column of big int values with empty entries ascending
0555  */
0556 void SpreadsheetTest::testSortSingleBigInt1() {
0557     const QVector<qint64> xData1{40000000000, 50000000000, 20000000000};
0558     const QVector<qint64> xData2{30000000000, 60000000000, -10000000000};
0559 
0560     Spreadsheet sheet(QStringLiteral("test"), false);
0561     sheet.setColumnCount(1);
0562     sheet.setRowCount(7);
0563     auto* col = sheet.column(0);
0564     col->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0565     col->replaceBigInt(0, xData1);
0566     col->replaceBigInt(4, xData2);
0567 
0568     // sort
0569     sheet.sortColumns(nullptr, {col}, true);
0570 
0571     // values
0572     QCOMPARE(col->bigIntAt(0), -10000000000ll);
0573     QCOMPARE(col->bigIntAt(1), 0);
0574     QCOMPARE(col->bigIntAt(2), 20000000000ll);
0575     QCOMPARE(col->bigIntAt(3), 30000000000ll);
0576     QCOMPARE(col->bigIntAt(4), 40000000000ll);
0577     QCOMPARE(col->bigIntAt(5), 50000000000ll);
0578     QCOMPARE(col->bigIntAt(6), 60000000000ll);
0579 }
0580 
0581 /*
0582  * check sorting single column of big int values with empty entries descending
0583  */
0584 void SpreadsheetTest::testSortSingleBigInt2() {
0585     const QVector<qint64> xData1{40000000000, 50000000000, 20000000000};
0586     const QVector<qint64> xData2{30000000000, 60000000000, -10000000000};
0587 
0588     Spreadsheet sheet(QStringLiteral("test"), false);
0589     sheet.setColumnCount(1);
0590     sheet.setRowCount(7);
0591     auto* col = sheet.column(0);
0592     col->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0593     col->replaceBigInt(0, xData1);
0594     col->replaceBigInt(4, xData2);
0595 
0596     // sort
0597     sheet.sortColumns(nullptr, {col}, false);
0598 
0599     // values
0600     QCOMPARE(col->bigIntAt(6), -10000000000ll);
0601     QCOMPARE(col->bigIntAt(5), 0);
0602     QCOMPARE(col->bigIntAt(4), 20000000000ll);
0603     QCOMPARE(col->bigIntAt(3), 30000000000ll);
0604     QCOMPARE(col->bigIntAt(2), 40000000000ll);
0605     QCOMPARE(col->bigIntAt(1), 50000000000ll);
0606     QCOMPARE(col->bigIntAt(0), 60000000000ll);
0607 }
0608 
0609 /*
0610  * check sorting single column of text with empty entries ascending
0611  */
0612 void SpreadsheetTest::testSortSingleText1() {
0613     const QVector<QString> xData{QStringLiteral("ben"),
0614                                  QStringLiteral("amy"),
0615                                  QStringLiteral("eddy"),
0616                                  QString(),
0617                                  QStringLiteral("carl"),
0618                                  QStringLiteral("dan")};
0619 
0620     Spreadsheet sheet(QStringLiteral("test"), false);
0621     sheet.setColumnCount(1);
0622     sheet.setRowCount(8);
0623     auto* col = sheet.column(0);
0624     col->setColumnMode(AbstractColumn::ColumnMode::Text);
0625     col->replaceTexts(0, xData);
0626 
0627     // sort
0628     sheet.sortColumns(nullptr, {col}, true);
0629 
0630     // values
0631     QCOMPARE(col->textAt(0), QLatin1String("amy"));
0632     QCOMPARE(col->textAt(1), QLatin1String("ben"));
0633     QCOMPARE(col->textAt(2), QLatin1String("carl"));
0634     QCOMPARE(col->textAt(3), QLatin1String("dan"));
0635     QCOMPARE(col->textAt(4), QLatin1String("eddy"));
0636     QCOMPARE(col->textAt(5), QString());
0637     QCOMPARE(col->textAt(6), QString());
0638 }
0639 
0640 /*
0641  * check sorting single column of text with empty entries descending
0642  */
0643 void SpreadsheetTest::testSortSingleText2() {
0644     const QVector<QString> xData =
0645         {QStringLiteral("ben"), QStringLiteral("amy"), QStringLiteral("eddy"), QString(), QStringLiteral("carl"), QStringLiteral("dan")};
0646 
0647     Spreadsheet sheet(QStringLiteral("test"), false);
0648     sheet.setColumnCount(1);
0649     sheet.setRowCount(8);
0650     auto* col = sheet.column(0);
0651     col->setColumnMode(AbstractColumn::ColumnMode::Text);
0652     col->replaceTexts(0, xData);
0653 
0654     // sort
0655     sheet.sortColumns(nullptr, {col}, false);
0656 
0657     // values
0658     QCOMPARE(col->textAt(0), QLatin1String("eddy"));
0659     QCOMPARE(col->textAt(1), QLatin1String("dan"));
0660     QCOMPARE(col->textAt(2), QLatin1String("carl"));
0661     QCOMPARE(col->textAt(3), QLatin1String("ben"));
0662     QCOMPARE(col->textAt(4), QLatin1String("amy"));
0663     QCOMPARE(col->textAt(5), QString());
0664     QCOMPARE(col->textAt(6), QString());
0665 }
0666 
0667 /*
0668  * check sorting single column of datetimes with invalid entries ascending
0669  */
0670 void SpreadsheetTest::testSortSingleDateTime1() {
0671     const QVector<QDateTime> xData{
0672         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)),
0673         QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)),
0674         QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)),
0675         QDateTime(QDate(2019, 02, 29), QTime(12, 12, 12)), // invalid
0676         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)),
0677         QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)),
0678     };
0679 
0680     Spreadsheet sheet(QStringLiteral("test"), false);
0681     sheet.setColumnCount(1);
0682     sheet.setRowCount(8);
0683     auto* col{sheet.column(0)};
0684     col->setColumnMode(AbstractColumn::ColumnMode::DateTime);
0685     col->replaceDateTimes(0, xData);
0686 
0687     // sort
0688     sheet.sortColumns(nullptr, {col}, true);
0689 
0690     // values
0691     QCOMPARE(col->dateTimeAt(0), QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)));
0692     QCOMPARE(col->dateTimeAt(1), QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)));
0693     QCOMPARE(col->dateTimeAt(2), QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)));
0694     QCOMPARE(col->dateTimeAt(3), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)));
0695     QCOMPARE(col->dateTimeAt(4), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)));
0696 }
0697 
0698 /*
0699  * check sorting single column of datetimes with invalid entries descending
0700  */
0701 void SpreadsheetTest::testSortSingleDateTime2() {
0702     const QVector<QDateTime> xData{
0703         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)),
0704         QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)),
0705         QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)),
0706         QDateTime(QDate(2019, 02, 29), QTime(12, 12, 12)), // invalid
0707         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)),
0708         QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)),
0709     };
0710 
0711     Spreadsheet sheet(QStringLiteral("test"), false);
0712     sheet.setColumnCount(1);
0713     sheet.setRowCount(8);
0714     auto* col = sheet.column(0);
0715     col->setColumnMode(AbstractColumn::ColumnMode::DateTime);
0716     col->replaceDateTimes(0, xData);
0717 
0718     // sort
0719     sheet.sortColumns(nullptr, {col}, false);
0720 
0721     // values
0722     QCOMPARE(col->dateTimeAt(4), QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)));
0723     QCOMPARE(col->dateTimeAt(3), QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)));
0724     QCOMPARE(col->dateTimeAt(2), QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)));
0725     QCOMPARE(col->dateTimeAt(1), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)));
0726     QCOMPARE(col->dateTimeAt(0), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)));
0727 }
0728 
0729 // multiple column
0730 /*
0731  * check sorting double values with NaN ascending as leading column
0732  */
0733 void SpreadsheetTest::testSortNumeric1() {
0734     const QVector<double> xData{0.5, -0.2, GSL_NAN, 2.0, -1.0};
0735     const QVector<int> yData{1, 2, 3, 4, 5, 6};
0736 
0737     Spreadsheet sheet(QStringLiteral("test"), false);
0738     sheet.setColumnCount(2);
0739     sheet.setRowCount(10);
0740     auto* col0{sheet.column(0)};
0741     auto* col1{sheet.column(1)};
0742     col0->replaceValues(0, xData);
0743     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
0744     col1->replaceInteger(0, yData);
0745 
0746     // sort
0747     sheet.sortColumns(col0, {col0, col1}, true);
0748 
0749     // values
0750     QCOMPARE(col0->valueAt(0), -1.0);
0751     QCOMPARE(col0->valueAt(1), -0.2);
0752     QCOMPARE(col0->valueAt(2), 0.5);
0753     QCOMPARE(col0->valueAt(3), 2.0);
0754     // QCOMPARE(col0->valueAt(4), GSL_NAN);
0755     QCOMPARE(col1->integerAt(0), 5);
0756     QCOMPARE(col1->integerAt(1), 2);
0757     QCOMPARE(col1->integerAt(2), 1);
0758     QCOMPARE(col1->integerAt(3), 4);
0759     QCOMPARE(col1->integerAt(4), 3);
0760     QCOMPARE(col1->integerAt(5), 6);
0761 }
0762 
0763 /*
0764  * check sorting double values with NaN descending as leading column
0765  */
0766 void SpreadsheetTest::testSortNumeric2() {
0767     const QVector<double> xData{0.5, -0.2, GSL_NAN, 2.0, -1.0};
0768     const QVector<int> yData{1, 2, 3, 4, 5, 6, 7};
0769 
0770     Spreadsheet sheet(QStringLiteral("test"), false);
0771     sheet.setColumnCount(2);
0772     sheet.setRowCount(10);
0773     auto* col0{sheet.column(0)};
0774     auto* col1{sheet.column(1)};
0775     col0->replaceValues(0, xData);
0776     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
0777     col1->replaceInteger(0, yData);
0778 
0779     // sort
0780     sheet.sortColumns(col0, {col0, col1}, false);
0781 
0782     // values
0783     QCOMPARE(col0->valueAt(0), 2.0);
0784     QCOMPARE(col0->valueAt(1), 0.5);
0785     QCOMPARE(col0->valueAt(2), -0.2);
0786     QCOMPARE(col0->valueAt(3), -1.0);
0787     QCOMPARE(col1->integerAt(0), 4);
0788     QCOMPARE(col1->integerAt(1), 1);
0789     QCOMPARE(col1->integerAt(2), 2);
0790     QCOMPARE(col1->integerAt(3), 5);
0791     QCOMPARE(col1->integerAt(4), 3);
0792     QCOMPARE(col1->integerAt(5), 6);
0793     QCOMPARE(col1->integerAt(6), 7);
0794 }
0795 
0796 /*
0797  * check sorting integer values with empty entries ascending as leading column
0798  */
0799 void SpreadsheetTest::testSortInteger1() {
0800     const QVector<int> xData1{4, 5, 2};
0801     const QVector<int> xData2{3, 6, -1};
0802     const QVector<int> yData{1, 2, 3, 4, 5, 6, 7};
0803 
0804     Spreadsheet sheet(QStringLiteral("test"), false);
0805     sheet.setColumnCount(2);
0806     sheet.setRowCount(7);
0807     auto* col0{sheet.column(0)};
0808     auto* col1{sheet.column(1)};
0809     col0->setColumnMode(AbstractColumn::ColumnMode::Integer);
0810     col0->replaceInteger(0, xData1);
0811     col0->replaceInteger(4, xData2);
0812     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
0813     col1->replaceInteger(0, yData);
0814 
0815     // sort
0816     sheet.sortColumns(col0, {col0, col1}, true);
0817 
0818     // values
0819     QCOMPARE(col0->integerAt(0), -1);
0820     QCOMPARE(col0->integerAt(1), 0);
0821     QCOMPARE(col0->integerAt(2), 2);
0822     QCOMPARE(col0->integerAt(3), 3);
0823     QCOMPARE(col0->integerAt(4), 4);
0824     QCOMPARE(col0->integerAt(5), 5);
0825     QCOMPARE(col0->integerAt(6), 6);
0826     QCOMPARE(col1->integerAt(0), 7);
0827     QCOMPARE(col1->integerAt(1), 4);
0828     QCOMPARE(col1->integerAt(2), 3);
0829     QCOMPARE(col1->integerAt(3), 5);
0830     QCOMPARE(col1->integerAt(4), 1);
0831     QCOMPARE(col1->integerAt(5), 2);
0832     QCOMPARE(col1->integerAt(6), 6);
0833 }
0834 
0835 /*
0836  * check sorting integer values with empty entries descending as leading column
0837  */
0838 void SpreadsheetTest::testSortInteger2() {
0839     const QVector<int> xData1{4, 5, 2};
0840     const QVector<int> xData2{3, 6, -1};
0841     const QVector<int> yData{1, 2, 3, 4, 5, 6, 7};
0842 
0843     Spreadsheet sheet(QStringLiteral("test"), false);
0844     sheet.setColumnCount(2);
0845     sheet.setRowCount(7);
0846     auto* col0{sheet.column(0)};
0847     auto* col1{sheet.column(1)};
0848     col0->setColumnMode(AbstractColumn::ColumnMode::Integer);
0849     col0->replaceInteger(0, xData1);
0850     col0->replaceInteger(4, xData2);
0851     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
0852     col1->replaceInteger(0, yData);
0853 
0854     // sort
0855     sheet.sortColumns(col0, {col0, col1}, false);
0856 
0857     // values
0858     QCOMPARE(col0->integerAt(6), -1);
0859     QCOMPARE(col0->integerAt(5), 0);
0860     QCOMPARE(col0->integerAt(4), 2);
0861     QCOMPARE(col0->integerAt(3), 3);
0862     QCOMPARE(col0->integerAt(2), 4);
0863     QCOMPARE(col0->integerAt(1), 5);
0864     QCOMPARE(col0->integerAt(0), 6);
0865     QCOMPARE(col1->integerAt(6), 7);
0866     QCOMPARE(col1->integerAt(5), 4);
0867     QCOMPARE(col1->integerAt(4), 3);
0868     QCOMPARE(col1->integerAt(3), 5);
0869     QCOMPARE(col1->integerAt(2), 1);
0870     QCOMPARE(col1->integerAt(1), 2);
0871     QCOMPARE(col1->integerAt(0), 6);
0872 }
0873 
0874 /*
0875  * check sorting big int values with empty entries ascending as leading column
0876  */
0877 void SpreadsheetTest::testSortBigInt1() {
0878     Spreadsheet sheet(QStringLiteral("test"), false);
0879     sheet.setColumnCount(2);
0880     sheet.setRowCount(7);
0881 
0882     QVector<qint64> xData1{40000000000, 50000000000, 20000000000};
0883     QVector<qint64> xData2{30000000000, 60000000000, -10000000000};
0884     QVector<qint64> yData{1, 2, 3, 4, 5, 6, 7};
0885 
0886     auto* col0{sheet.column(0)};
0887     auto* col1{sheet.column(1)};
0888     col0->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0889     col0->replaceBigInt(0, xData1);
0890     col0->replaceBigInt(4, xData2);
0891     col1->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0892     col1->replaceBigInt(0, yData);
0893 
0894     // sort
0895     sheet.sortColumns(col0, {col0, col1}, true);
0896 
0897     // values
0898     QCOMPARE(col0->bigIntAt(0), -10000000000ll);
0899     QCOMPARE(col0->bigIntAt(1), 0);
0900     QCOMPARE(col0->bigIntAt(2), 20000000000ll);
0901     QCOMPARE(col0->bigIntAt(3), 30000000000ll);
0902     QCOMPARE(col0->bigIntAt(4), 40000000000ll);
0903     QCOMPARE(col0->bigIntAt(5), 50000000000ll);
0904     QCOMPARE(col0->bigIntAt(6), 60000000000ll);
0905     QCOMPARE(col1->bigIntAt(0), 7);
0906     QCOMPARE(col1->bigIntAt(1), 4);
0907     QCOMPARE(col1->bigIntAt(2), 3);
0908     QCOMPARE(col1->bigIntAt(3), 5);
0909     QCOMPARE(col1->bigIntAt(4), 1);
0910     QCOMPARE(col1->bigIntAt(5), 2);
0911     QCOMPARE(col1->bigIntAt(6), 6);
0912 }
0913 
0914 /*
0915  * check sorting big int values with empty entries descending as leading column
0916  */
0917 void SpreadsheetTest::testSortBigInt2() {
0918     Spreadsheet sheet(QStringLiteral("test"), false);
0919     sheet.setColumnCount(2);
0920     sheet.setRowCount(7);
0921 
0922     QVector<qint64> xData1{40000000000, 50000000000, 20000000000};
0923     QVector<qint64> xData2{30000000000, 60000000000, -10000000000};
0924     QVector<qint64> yData{1, 2, 3, 4, 5, 6, 7};
0925 
0926     auto* col0{sheet.column(0)};
0927     auto* col1{sheet.column(1)};
0928     col0->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0929     col0->replaceBigInt(0, xData1);
0930     col0->replaceBigInt(4, xData2);
0931     col1->setColumnMode(AbstractColumn::ColumnMode::BigInt);
0932     col1->replaceBigInt(0, yData);
0933 
0934     // sort
0935     sheet.sortColumns(col0, {col0, col1}, false);
0936 
0937     // values
0938     QCOMPARE(col0->bigIntAt(6), -10000000000ll);
0939     QCOMPARE(col0->bigIntAt(5), 0);
0940     QCOMPARE(col0->bigIntAt(4), 20000000000ll);
0941     QCOMPARE(col0->bigIntAt(3), 30000000000ll);
0942     QCOMPARE(col0->bigIntAt(2), 40000000000ll);
0943     QCOMPARE(col0->bigIntAt(1), 50000000000ll);
0944     QCOMPARE(col0->bigIntAt(0), 60000000000ll);
0945     QCOMPARE(col1->bigIntAt(6), 7);
0946     QCOMPARE(col1->bigIntAt(5), 4);
0947     QCOMPARE(col1->bigIntAt(4), 3);
0948     QCOMPARE(col1->bigIntAt(3), 5);
0949     QCOMPARE(col1->bigIntAt(2), 1);
0950     QCOMPARE(col1->bigIntAt(1), 2);
0951     QCOMPARE(col1->bigIntAt(0), 6);
0952 }
0953 
0954 /*
0955  * check sorting text with empty entries ascending as leading column
0956  */
0957 void SpreadsheetTest::testSortText1() {
0958     Spreadsheet sheet(QStringLiteral("test"), false);
0959     sheet.setColumnCount(2);
0960     sheet.setRowCount(8);
0961 
0962     QVector<QString> xData{QStringLiteral("ben"), QStringLiteral("amy"), QStringLiteral("eddy"), QString(), QStringLiteral("carl"), QStringLiteral("dan")};
0963     QVector<int> yData{1, 2, 3, 4, 5, 6, 7};
0964 
0965     auto* col0{sheet.column(0)};
0966     auto* col1{sheet.column(1)};
0967     col0->setColumnMode(AbstractColumn::ColumnMode::Text);
0968     col0->replaceTexts(0, xData);
0969     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
0970     col1->replaceInteger(0, yData);
0971 
0972     // sort
0973     sheet.sortColumns(col0, {col0, col1}, true);
0974 
0975     // values
0976     QCOMPARE(col0->textAt(0), QLatin1String("amy"));
0977     QCOMPARE(col0->textAt(1), QLatin1String("ben"));
0978     QCOMPARE(col0->textAt(2), QLatin1String("carl"));
0979     QCOMPARE(col0->textAt(3), QLatin1String("dan"));
0980     QCOMPARE(col0->textAt(4), QLatin1String("eddy"));
0981     QCOMPARE(col0->textAt(5), QString());
0982     QCOMPARE(col0->textAt(6), QString());
0983     QCOMPARE(col1->integerAt(0), 2);
0984     QCOMPARE(col1->integerAt(1), 1);
0985     QCOMPARE(col1->integerAt(2), 5);
0986     QCOMPARE(col1->integerAt(3), 6);
0987     QCOMPARE(col1->integerAt(4), 3);
0988     QCOMPARE(col1->integerAt(5), 4);
0989     QCOMPARE(col1->integerAt(6), 7);
0990 }
0991 
0992 /*
0993  * check sorting text with empty entries descending as leading column
0994  */
0995 void SpreadsheetTest::testSortText2() {
0996     Spreadsheet sheet(QStringLiteral("test"), false);
0997     sheet.setColumnCount(2);
0998     sheet.setRowCount(8);
0999 
1000     QVector<QString> xData{QStringLiteral("ben"), QStringLiteral("amy"), QStringLiteral("eddy"), QString(), QStringLiteral("carl"), QStringLiteral("dan")};
1001     QVector<int> yData{1, 2, 3, 4, 5, 6, 7};
1002 
1003     auto* col0{sheet.column(0)};
1004     auto* col1{sheet.column(1)};
1005     col0->setColumnMode(AbstractColumn::ColumnMode::Text);
1006     col0->replaceTexts(0, xData);
1007     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
1008     col1->replaceInteger(0, yData);
1009 
1010     // sort
1011     sheet.sortColumns(col0, {col0, col1}, false);
1012 
1013     // values
1014     QCOMPARE(col0->textAt(4), QLatin1String("amy"));
1015     QCOMPARE(col0->textAt(3), QLatin1String("ben"));
1016     QCOMPARE(col0->textAt(2), QLatin1String("carl"));
1017     QCOMPARE(col0->textAt(1), QLatin1String("dan"));
1018     QCOMPARE(col0->textAt(0), QLatin1String("eddy"));
1019     QCOMPARE(col0->textAt(5), QString());
1020     QCOMPARE(col0->textAt(6), QString());
1021     QCOMPARE(col1->integerAt(4), 2);
1022     QCOMPARE(col1->integerAt(3), 1);
1023     QCOMPARE(col1->integerAt(2), 5);
1024     QCOMPARE(col1->integerAt(1), 6);
1025     QCOMPARE(col1->integerAt(0), 3);
1026     QCOMPARE(col1->integerAt(5), 4);
1027     QCOMPARE(col1->integerAt(6), 7);
1028 }
1029 
1030 /*
1031  * check sorting datetimes with invalid entries ascending as leading column
1032  */
1033 void SpreadsheetTest::testSortDateTime1() {
1034     Spreadsheet sheet(QStringLiteral("test"), false);
1035     sheet.setColumnCount(2);
1036     sheet.setRowCount(8);
1037 
1038     QVector<QDateTime> xData{
1039         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)),
1040         QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)),
1041         QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)),
1042         QDateTime(QDate(2019, 02, 29), QTime(12, 12, 12)), // invalid
1043         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)),
1044         QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)),
1045     };
1046     QVector<int> yData = {1, 2, 3, 4, 5, 6, 7};
1047 
1048     auto* col0{sheet.column(0)};
1049     auto* col1{sheet.column(1)};
1050     col0->setColumnMode(AbstractColumn::ColumnMode::DateTime);
1051     col0->replaceDateTimes(0, xData);
1052     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
1053     col1->replaceInteger(0, yData);
1054 
1055     // sort
1056     sheet.sortColumns(col0, {col0, col1}, true);
1057 
1058     // values
1059     QCOMPARE(col0->dateTimeAt(0), QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)));
1060     QCOMPARE(col0->dateTimeAt(1), QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)));
1061     QCOMPARE(col0->dateTimeAt(2), QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)));
1062     QCOMPARE(col0->dateTimeAt(3), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)));
1063     QCOMPARE(col0->dateTimeAt(4), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)));
1064     QCOMPARE(col1->integerAt(0), 3);
1065     QCOMPARE(col1->integerAt(1), 2);
1066     QCOMPARE(col1->integerAt(2), 6);
1067     QCOMPARE(col1->integerAt(3), 1);
1068     QCOMPARE(col1->integerAt(4), 5);
1069     QCOMPARE(col1->integerAt(5), 4);
1070     QCOMPARE(col1->integerAt(6), 7);
1071 }
1072 
1073 /*
1074  * check sorting datetimes with invalid entries descending as leading column
1075  */
1076 void SpreadsheetTest::testSortDateTime2() {
1077     const QVector<QDateTime> xData{
1078         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)),
1079         QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)),
1080         QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)),
1081         QDateTime(QDate(2019, 02, 29), QTime(12, 12, 12)), // invalid
1082         QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)),
1083         QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)),
1084     };
1085     const QVector<int> yData = {1, 2, 3, 4, 5, 6, 7};
1086 
1087     Spreadsheet sheet(QStringLiteral("test"), false);
1088     sheet.setColumnCount(2);
1089     sheet.setRowCount(8);
1090     auto* col0{sheet.column(0)};
1091     auto* col1{sheet.column(1)};
1092     col0->setColumnMode(AbstractColumn::ColumnMode::DateTime);
1093     col0->replaceDateTimes(0, xData);
1094     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
1095     col1->replaceInteger(0, yData);
1096 
1097     // sort
1098     sheet.sortColumns(col0, {col0, col1}, false);
1099 
1100     // values
1101     QCOMPARE(col0->dateTimeAt(4), QDateTime(QDate(2019, 02, 28), QTime(12, 12, 12)));
1102     QCOMPARE(col0->dateTimeAt(3), QDateTime(QDate(2020, 02, 28), QTime(12, 12, 12)));
1103     QCOMPARE(col0->dateTimeAt(2), QDateTime(QDate(2020, 02, 29), QTime(11, 12, 12)));
1104     QCOMPARE(col0->dateTimeAt(1), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 12)));
1105     QCOMPARE(col0->dateTimeAt(0), QDateTime(QDate(2020, 02, 29), QTime(12, 12, 13)));
1106     QCOMPARE(col1->integerAt(4), 3);
1107     QCOMPARE(col1->integerAt(3), 2);
1108     QCOMPARE(col1->integerAt(2), 6);
1109     QCOMPARE(col1->integerAt(1), 1);
1110     QCOMPARE(col1->integerAt(0), 5);
1111     QCOMPARE(col1->integerAt(5), 4);
1112     QCOMPARE(col1->integerAt(6), 7);
1113 }
1114 
1115 // performance
1116 
1117 /*
1118  * check performance of sorting double values in single column
1119  */
1120 void SpreadsheetTest::testSortPerformanceNumeric1() {
1121     Spreadsheet sheet(QStringLiteral("test"), false);
1122     sheet.setColumnCount(1);
1123     sheet.setRowCount(10000);
1124 
1125     QVector<double> xData;
1126     WARN("CREATE DATA")
1127     for (int i = 0; i < sheet.rowCount(); i++)
1128 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
1129         xData << QRandomGenerator::global()->generateDouble();
1130 #else
1131         xData << (double)(qrand()) / RAND_MAX;
1132 #endif
1133 
1134     auto* col = sheet.column(0);
1135     col->replaceValues(0, xData);
1136 
1137     // sort
1138     QBENCHMARK { sheet.sortColumns(nullptr, {col}, true); }
1139 }
1140 
1141 /*
1142  * check performance of sorting double values with two columns
1143  */
1144 void SpreadsheetTest::testSortPerformanceNumeric2() {
1145     Spreadsheet sheet(QStringLiteral("test"), false);
1146     sheet.setColumnCount(2);
1147     sheet.setRowCount(10000);
1148 
1149     QVector<double> xData;
1150     QVector<int> yData;
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         yData << i + 1;
1159     }
1160 
1161     auto* col0{sheet.column(0)};
1162     auto* col1{sheet.column(1)};
1163     col0->replaceValues(0, xData);
1164     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
1165     col1->replaceInteger(0, yData);
1166 
1167     // sort
1168     QBENCHMARK { sheet.sortColumns(col0, {col0, col1}, true); }
1169 }
1170 
1171 void SpreadsheetTest::testFlatten00() {
1172     Project project;
1173     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1174     project.addChild(sheet);
1175     sheet->setColumnCount(5);
1176     sheet->setRowCount(4);
1177 
1178     // "Year"
1179     auto* col1 = sheet->column(0);
1180     col1->setName(QStringLiteral("Year"));
1181     col1->setColumnMode(AbstractColumn::ColumnMode::Text);
1182     col1->setTextAt(0, QStringLiteral("2021"));
1183     col1->setTextAt(1, QStringLiteral("2022"));
1184     col1->setTextAt(2, QStringLiteral("2021"));
1185     col1->setTextAt(3, QStringLiteral("2022"));
1186 
1187     // "Country"
1188     auto* col2 = sheet->column(1);
1189     col2->setName(QStringLiteral("Country"));
1190     col2->setColumnMode(AbstractColumn::ColumnMode::Text);
1191     col2->setTextAt(0, QStringLiteral("Germany"));
1192     col2->setTextAt(1, QStringLiteral("Germany"));
1193     col2->setTextAt(2, QStringLiteral("Poland"));
1194     col2->setTextAt(3, QStringLiteral("Poland"));
1195 
1196     // "Sales for Product 1"
1197     auto* col3 = sheet->column(2);
1198     col3->setName(QStringLiteral("Product 1"));
1199     col3->setColumnMode(AbstractColumn::ColumnMode::Integer);
1200     col3->setIntegerAt(0, 1);
1201     col3->setIntegerAt(1, 10);
1202     col3->setIntegerAt(2, 4);
1203     col3->setIntegerAt(3, 40);
1204 
1205     // "Sales for Product 2"
1206     auto* col4 = sheet->column(3);
1207     col4->setName(QStringLiteral("Product 2"));
1208     col4->setColumnMode(AbstractColumn::ColumnMode::Integer);
1209     col4->setIntegerAt(0, 2);
1210     col4->setIntegerAt(1, 20);
1211     col4->setIntegerAt(2, 5);
1212     col4->setIntegerAt(3, 50);
1213 
1214     // "Sales for Product 3"
1215     auto* col5 = sheet->column(4);
1216     col5->setName(QStringLiteral("Product 3"));
1217     col5->setColumnMode(AbstractColumn::ColumnMode::Integer);
1218     col5->setIntegerAt(0, 3);
1219     col5->setIntegerAt(1, 30);
1220     col5->setIntegerAt(2, 6);
1221     col5->setIntegerAt(3, 60);
1222 
1223     // flatten the product columns relatively to year and country
1224     FlattenColumnsDialog dlg(sheet);
1225     QVector<Column*> referenceColumns;
1226     referenceColumns << col1;
1227     referenceColumns << col2;
1228 
1229     QVector<Column*> valueColumns;
1230     valueColumns << col3;
1231     valueColumns << col4;
1232     valueColumns << col5;
1233 
1234     dlg.flatten(sheet, valueColumns, referenceColumns);
1235 
1236     // checks
1237     // make sure a new target spreadsheet with the flattened data was created
1238     const auto& sheets = project.children<Spreadsheet>();
1239     QCOMPARE(sheets.count(), 2);
1240     auto* targetSheet = sheets.at(1);
1241     QCOMPARE(targetSheet->columnCount(), 4); // two reference columns, column "Category" and column "Value"
1242     QCOMPARE(targetSheet->rowCount(), 12);
1243 
1244     // check values
1245     col1 = targetSheet->column(0);
1246     QCOMPARE(col1->textAt(0), QStringLiteral("2021"));
1247     QCOMPARE(col1->textAt(1), QStringLiteral("2021"));
1248     QCOMPARE(col1->textAt(2), QStringLiteral("2021"));
1249     QCOMPARE(col1->textAt(3), QStringLiteral("2022"));
1250     QCOMPARE(col1->textAt(4), QStringLiteral("2022"));
1251     QCOMPARE(col1->textAt(5), QStringLiteral("2022"));
1252     QCOMPARE(col1->textAt(6), QStringLiteral("2021"));
1253     QCOMPARE(col1->textAt(7), QStringLiteral("2021"));
1254     QCOMPARE(col1->textAt(8), QStringLiteral("2021"));
1255     QCOMPARE(col1->textAt(9), QStringLiteral("2022"));
1256     QCOMPARE(col1->textAt(10), QStringLiteral("2022"));
1257     QCOMPARE(col1->textAt(11), QStringLiteral("2022"));
1258 
1259     col4 = targetSheet->column(3);
1260     QCOMPARE(col4->integerAt(0), 1);
1261     QCOMPARE(col4->integerAt(1), 2);
1262     QCOMPARE(col4->integerAt(2), 3);
1263     QCOMPARE(col4->integerAt(3), 10);
1264     QCOMPARE(col4->integerAt(4), 20);
1265     QCOMPARE(col4->integerAt(5), 30);
1266     QCOMPARE(col4->integerAt(6), 4);
1267     QCOMPARE(col4->integerAt(7), 5);
1268     QCOMPARE(col4->integerAt(8), 6);
1269     QCOMPARE(col4->integerAt(9), 40);
1270     QCOMPARE(col4->integerAt(10), 50);
1271     QCOMPARE(col4->integerAt(11), 60);
1272 }
1273 
1274 // test with a missing value in one of the reference columns
1275 void SpreadsheetTest::testFlatten01() {
1276     Project project;
1277     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1278     project.addChild(sheet);
1279     sheet->setColumnCount(5);
1280     sheet->setRowCount(2);
1281 
1282     // "Year"
1283     auto* col1 = sheet->column(0);
1284     col1->setName(QStringLiteral("Year"));
1285     col1->setColumnMode(AbstractColumn::ColumnMode::Text);
1286     col1->setTextAt(0, QStringLiteral("2021"));
1287     col1->setTextAt(1, QStringLiteral("2022"));
1288 
1289     // "Country"
1290     auto* col2 = sheet->column(1);
1291     col2->setName(QStringLiteral("Country"));
1292     col2->setColumnMode(AbstractColumn::ColumnMode::Text);
1293     col2->setTextAt(0, QStringLiteral("Germany"));
1294     // missing value in the second row
1295 
1296     // "Sales for Product 1"
1297     auto* col3 = sheet->column(2);
1298     col3->setName(QStringLiteral("Product 1"));
1299     col3->setColumnMode(AbstractColumn::ColumnMode::Integer);
1300     col3->setIntegerAt(0, 1);
1301     col3->setIntegerAt(1, 10);
1302 
1303     // "Sales for Product 2"
1304     auto* col4 = sheet->column(3);
1305     col4->setName(QStringLiteral("Product 2"));
1306     col4->setColumnMode(AbstractColumn::ColumnMode::Integer);
1307     col4->setIntegerAt(0, 2);
1308     col4->setIntegerAt(1, 20);
1309 
1310     // "Sales for Product 3"
1311     auto* col5 = sheet->column(4);
1312     col5->setName(QStringLiteral("Product 3"));
1313     col5->setColumnMode(AbstractColumn::ColumnMode::Integer);
1314     col5->setIntegerAt(0, 3);
1315     col5->setIntegerAt(1, 30);
1316 
1317     // flatten the product columns relatively to year and country
1318     FlattenColumnsDialog dlg(sheet);
1319     QVector<Column*> referenceColumns;
1320     referenceColumns << col1;
1321     referenceColumns << col2;
1322 
1323     QVector<Column*> valueColumns;
1324     valueColumns << col3;
1325     valueColumns << col4;
1326     valueColumns << col5;
1327 
1328     dlg.flatten(sheet, valueColumns, referenceColumns);
1329 
1330     // checks
1331     // make sure a new target spreadsheet with the flattened data was created
1332     const auto& sheets = project.children<Spreadsheet>();
1333     QCOMPARE(sheets.count(), 2);
1334     auto* targetSheet = sheets.at(1);
1335     QCOMPARE(targetSheet->columnCount(), 4); // two reference columns, column "Category" and column "Value"
1336     QCOMPARE(targetSheet->rowCount(), 6);
1337 
1338     // check values
1339     col1 = targetSheet->column(0);
1340     QCOMPARE(col1->textAt(0), QStringLiteral("2021"));
1341     QCOMPARE(col1->textAt(1), QStringLiteral("2021"));
1342     QCOMPARE(col1->textAt(2), QStringLiteral("2021"));
1343     QCOMPARE(col1->textAt(3), QStringLiteral("2022"));
1344     QCOMPARE(col1->textAt(4), QStringLiteral("2022"));
1345     QCOMPARE(col1->textAt(5), QStringLiteral("2022"));
1346 
1347     col1 = targetSheet->column(1);
1348     QCOMPARE(col1->textAt(0), QStringLiteral("Germany"));
1349     QCOMPARE(col1->textAt(1), QStringLiteral("Germany"));
1350     QCOMPARE(col1->textAt(2), QStringLiteral("Germany"));
1351     QCOMPARE(col1->textAt(3), QString());
1352     QCOMPARE(col1->textAt(4), QString());
1353     QCOMPARE(col1->textAt(5), QString());
1354 
1355     col4 = targetSheet->column(3);
1356     QCOMPARE(col4->integerAt(0), 1);
1357     QCOMPARE(col4->integerAt(1), 2);
1358     QCOMPARE(col4->integerAt(2), 3);
1359     QCOMPARE(col4->integerAt(3), 10);
1360     QCOMPARE(col4->integerAt(4), 20);
1361     QCOMPARE(col4->integerAt(5), 30);
1362 }
1363 
1364 // test with missing values in the reference columns - no result should be produced for these rows
1365 void SpreadsheetTest::testFlatten02() {
1366     Project project;
1367     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1368     project.addChild(sheet);
1369     sheet->setColumnCount(5);
1370     sheet->setRowCount(2);
1371 
1372     // "Year"
1373     auto* col1 = sheet->column(0);
1374     col1->setName(QStringLiteral("Year"));
1375     col1->setColumnMode(AbstractColumn::ColumnMode::Text);
1376     col1->setTextAt(0, QStringLiteral("2021"));
1377     // missing value in the second row
1378 
1379     // "Country"
1380     auto* col2 = sheet->column(1);
1381     col2->setName(QStringLiteral("Country"));
1382     col2->setColumnMode(AbstractColumn::ColumnMode::Text);
1383     col2->setTextAt(0, QStringLiteral("Germany"));
1384     // missing value in the second rows
1385 
1386     // "Sales for Product 1"
1387     auto* col3 = sheet->column(2);
1388     col3->setName(QStringLiteral("Product 1"));
1389     col3->setColumnMode(AbstractColumn::ColumnMode::Integer);
1390     col3->setIntegerAt(0, 1);
1391     col3->setIntegerAt(1, 10);
1392 
1393     // "Sales for Product 2"
1394     auto* col4 = sheet->column(3);
1395     col4->setName(QStringLiteral("Product 2"));
1396     col4->setColumnMode(AbstractColumn::ColumnMode::Integer);
1397     col4->setIntegerAt(0, 2);
1398     col4->setIntegerAt(1, 20);
1399 
1400     // "Sales for Product 3"
1401     auto* col5 = sheet->column(4);
1402     col5->setName(QStringLiteral("Product 3"));
1403     col5->setColumnMode(AbstractColumn::ColumnMode::Integer);
1404     col5->setIntegerAt(0, 3);
1405     col5->setIntegerAt(1, 30);
1406 
1407     // flatten the product columns relatively to year and country
1408     FlattenColumnsDialog dlg(sheet);
1409     QVector<Column*> referenceColumns;
1410     referenceColumns << col1;
1411     referenceColumns << col2;
1412 
1413     QVector<Column*> valueColumns;
1414     valueColumns << col3;
1415     valueColumns << col4;
1416     valueColumns << col5;
1417 
1418     dlg.flatten(sheet, valueColumns, referenceColumns);
1419 
1420     // checks
1421     // make sure a new target spreadsheet with the flattened data was created
1422     const auto& sheets = project.children<Spreadsheet>();
1423     QCOMPARE(sheets.count(), 2);
1424     auto* targetSheet = sheets.at(1);
1425     QCOMPARE(targetSheet->columnCount(), 4); // two reference columns, column "Category" and column "Value"
1426     QCOMPARE(targetSheet->rowCount(), 3);
1427 
1428     // check values
1429     col1 = targetSheet->column(0);
1430     QCOMPARE(col1->textAt(0), QStringLiteral("2021"));
1431     QCOMPARE(col1->textAt(1), QStringLiteral("2021"));
1432     QCOMPARE(col1->textAt(2), QStringLiteral("2021"));
1433 
1434     col1 = targetSheet->column(1);
1435     QCOMPARE(col1->textAt(0), QStringLiteral("Germany"));
1436     QCOMPARE(col1->textAt(1), QStringLiteral("Germany"));
1437     QCOMPARE(col1->textAt(2), QStringLiteral("Germany"));
1438 
1439     col4 = targetSheet->column(3);
1440     QCOMPARE(col4->integerAt(0), 1);
1441     QCOMPARE(col4->integerAt(1), 2);
1442     QCOMPARE(col4->integerAt(2), 3);
1443 }
1444 
1445 // test with missing no reference columns
1446 void SpreadsheetTest::testFlatten03() {
1447     Project project;
1448     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1449     project.addChild(sheet);
1450     sheet->setColumnCount(3);
1451     sheet->setRowCount(2);
1452 
1453     // "Sales for Product 1"
1454     auto* col1 = sheet->column(0);
1455     col1->setName(QStringLiteral("Product 1"));
1456     col1->setColumnMode(AbstractColumn::ColumnMode::Integer);
1457     col1->setIntegerAt(0, 1);
1458     col1->setIntegerAt(1, 10);
1459 
1460     // "Sales for Product 2"
1461     auto* col2 = sheet->column(1);
1462     col2->setName(QStringLiteral("Product 2"));
1463     col2->setColumnMode(AbstractColumn::ColumnMode::Integer);
1464     col2->setIntegerAt(0, 2);
1465     col2->setIntegerAt(1, 20);
1466 
1467     // "Sales for Product 3"
1468     auto* col3 = sheet->column(2);
1469     col3->setName(QStringLiteral("Product 3"));
1470     col3->setColumnMode(AbstractColumn::ColumnMode::Integer);
1471     col3->setIntegerAt(0, 3);
1472     col3->setIntegerAt(1, 30);
1473 
1474     // flatten the product columns without any reference columns
1475     QVector<Column*> valueColumns;
1476     valueColumns << col1;
1477     valueColumns << col2;
1478     valueColumns << col3;
1479 
1480     FlattenColumnsDialog dlg(sheet);
1481     dlg.flatten(sheet, valueColumns, QVector<Column*>());
1482 
1483     // checks
1484     // make sure a new target spreadsheet with the flattened data was created
1485     const auto& sheets = project.children<Spreadsheet>();
1486     QCOMPARE(sheets.count(), 2);
1487     auto* targetSheet = sheets.at(1);
1488     QCOMPARE(targetSheet->columnCount(), 2); // no reference columns, only column "Category" and column "Value"
1489     QCOMPARE(targetSheet->rowCount(), 6);
1490 
1491     // check values
1492     col1 = targetSheet->column(0);
1493     QCOMPARE(col1->textAt(0), QStringLiteral("Product 1"));
1494     QCOMPARE(col1->textAt(1), QStringLiteral("Product 2"));
1495     QCOMPARE(col1->textAt(2), QStringLiteral("Product 3"));
1496     QCOMPARE(col1->textAt(3), QStringLiteral("Product 1"));
1497     QCOMPARE(col1->textAt(4), QStringLiteral("Product 2"));
1498     QCOMPARE(col1->textAt(5), QStringLiteral("Product 3"));
1499 
1500     col2 = targetSheet->column(1);
1501     QCOMPARE(col2->integerAt(0), 1);
1502     QCOMPARE(col2->integerAt(1), 2);
1503     QCOMPARE(col2->integerAt(2), 3);
1504     QCOMPARE(col2->integerAt(3), 10);
1505     QCOMPARE(col2->integerAt(4), 20);
1506     QCOMPARE(col2->integerAt(5), 30);
1507 }
1508 
1509 void SpreadsheetTest::testInsertRows() {
1510     Project project;
1511     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1512     project.addChild(sheet);
1513 
1514     auto* model = new SpreadsheetModel(sheet);
1515     int rowsAboutToBeInsertedCounter = 0;
1516     connect(model, &SpreadsheetModel::rowsAboutToBeInserted, [this, &rowsAboutToBeInsertedCounter]() {
1517         rowsAboutToBeInsertedCounter++;
1518     });
1519     int rowsInsertedCounter = 0;
1520     connect(model, &SpreadsheetModel::rowsInserted, [this, &rowsInsertedCounter]() {
1521         rowsInsertedCounter++;
1522     });
1523     int rowsAboutToBeRemovedCounter = 0;
1524     connect(model, &SpreadsheetModel::rowsAboutToBeRemoved, [this, &rowsAboutToBeRemovedCounter]() {
1525         rowsAboutToBeRemovedCounter++;
1526     });
1527     int rowsRemovedCounter = 0;
1528     connect(model, &SpreadsheetModel::rowsRemoved, [this, &rowsRemovedCounter]() {
1529         rowsRemovedCounter++;
1530     });
1531 
1532     QCOMPARE(sheet->rowCount(), 100);
1533     sheet->setRowCount(101); // No crash shall happen
1534     QCOMPARE(sheet->rowCount(), 101);
1535 
1536     sheet->undoStack()->undo();
1537     QCOMPARE(sheet->rowCount(), 100);
1538     sheet->undoStack()->redo();
1539     QCOMPARE(sheet->rowCount(), 101);
1540 
1541     QCOMPARE(rowsAboutToBeInsertedCounter, 2); // set and redo()
1542     QCOMPARE(rowsInsertedCounter, 2); // set and redo()
1543     QCOMPARE(rowsAboutToBeRemovedCounter, 1); // undo()
1544     QCOMPARE(rowsRemovedCounter, 1); // undo()
1545 }
1546 
1547 void SpreadsheetTest::testRemoveRows() {
1548     Project project;
1549     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1550     project.addChild(sheet);
1551 
1552     auto* model = new SpreadsheetModel(sheet);
1553     int rowsAboutToBeInsertedCounter = 0;
1554     connect(model, &SpreadsheetModel::rowsAboutToBeInserted, [this, &rowsAboutToBeInsertedCounter]() {
1555         rowsAboutToBeInsertedCounter++;
1556     });
1557     int rowsInsertedCounter = 0;
1558     connect(model, &SpreadsheetModel::rowsInserted, [this, &rowsInsertedCounter]() {
1559         rowsInsertedCounter++;
1560     });
1561     int rowsAboutToBeRemovedCounter = 0;
1562     connect(model, &SpreadsheetModel::rowsAboutToBeRemoved, [this, &rowsAboutToBeRemovedCounter]() {
1563         rowsAboutToBeRemovedCounter++;
1564     });
1565     int rowsRemovedCounter = 0;
1566     connect(model, &SpreadsheetModel::rowsRemoved, [this, &rowsRemovedCounter]() {
1567         rowsRemovedCounter++;
1568     });
1569 
1570     QCOMPARE(sheet->rowCount(), 100);
1571     sheet->setRowCount(10); // No crash shall happen
1572     QCOMPARE(sheet->rowCount(), 10);
1573 
1574     sheet->undoStack()->undo();
1575     QCOMPARE(sheet->rowCount(), 100);
1576     sheet->undoStack()->redo();
1577     QCOMPARE(sheet->rowCount(), 10);
1578 
1579     QCOMPARE(rowsAboutToBeInsertedCounter, 1); // undo
1580     QCOMPARE(rowsInsertedCounter, 1); // undo
1581     QCOMPARE(rowsAboutToBeRemovedCounter, 2); // set and redo()
1582     QCOMPARE(rowsRemovedCounter, 2); // set and redo()
1583 }
1584 
1585 void SpreadsheetTest::testInsertColumns() {
1586     Project project;
1587     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1588     project.addChild(sheet);
1589 
1590     auto* model = new SpreadsheetModel(sheet);
1591 
1592     int columnsAboutToBeInsertedCounter = 0;
1593     connect(model, &SpreadsheetModel::columnsAboutToBeInserted, [this, &columnsAboutToBeInsertedCounter]() {
1594         columnsAboutToBeInsertedCounter++;
1595     });
1596     int columnsInsertedCounter = 0;
1597     connect(model, &SpreadsheetModel::columnsInserted, [this, &columnsInsertedCounter]() {
1598         columnsInsertedCounter++;
1599     });
1600     int columnsAboutToBeRemovedCounter = 0;
1601     connect(model, &SpreadsheetModel::columnsAboutToBeRemoved, [this, &columnsAboutToBeRemovedCounter]() {
1602         columnsAboutToBeRemovedCounter++;
1603     });
1604     int columnsRemovedCounter = 0;
1605     connect(model, &SpreadsheetModel::columnsRemoved, [this, &columnsRemovedCounter]() {
1606         columnsRemovedCounter++;
1607     });
1608 
1609     QCOMPARE(sheet->columnCount(), 2);
1610     sheet->setColumnCount(5); // No crash shall happen
1611     QCOMPARE(sheet->columnCount(), 5);
1612 
1613     sheet->undoStack()->undo();
1614     QCOMPARE(sheet->columnCount(), 2);
1615     sheet->undoStack()->redo();
1616     QCOMPARE(sheet->columnCount(), 5);
1617 
1618     QCOMPARE(columnsAboutToBeInsertedCounter, 2); // set and redo()
1619     QCOMPARE(columnsInsertedCounter, 2); // set and redo()
1620     QCOMPARE(columnsRemovedCounter, 1); // undo()
1621     QCOMPARE(columnsAboutToBeRemovedCounter, 1); // undo()
1622 }
1623 
1624 void SpreadsheetTest::testRemoveColumns() {
1625     Project project;
1626     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1627     project.addChild(sheet);
1628 
1629     auto* model = new SpreadsheetModel(sheet);
1630 
1631     int columnsAboutToBeInsertedCounter = 0;
1632     connect(model, &SpreadsheetModel::columnsAboutToBeInserted, [this, &columnsAboutToBeInsertedCounter]() {
1633         columnsAboutToBeInsertedCounter++;
1634     });
1635     int columnsInsertedCounter = 0;
1636     connect(model, &SpreadsheetModel::columnsInserted, [this, &columnsInsertedCounter]() {
1637         columnsInsertedCounter++;
1638     });
1639     int columnsAboutToBeRemovedCounter = 0;
1640     connect(model, &SpreadsheetModel::columnsAboutToBeRemoved, [this, &columnsAboutToBeRemovedCounter]() {
1641         columnsAboutToBeRemovedCounter++;
1642     });
1643     int columnsRemovedCounter = 0;
1644     connect(model, &SpreadsheetModel::columnsRemoved, [this, &columnsRemovedCounter]() {
1645         columnsRemovedCounter++;
1646     });
1647 
1648     QCOMPARE(sheet->columnCount(), 2);
1649     sheet->setColumnCount(1); // No crash shall happen
1650     QCOMPARE(sheet->columnCount(), 1);
1651 
1652     sheet->undoStack()->undo();
1653     QCOMPARE(sheet->columnCount(), 2);
1654     sheet->undoStack()->redo();
1655     QCOMPARE(sheet->columnCount(), 1);
1656 
1657     QCOMPARE(columnsAboutToBeInsertedCounter, 1); // undo()
1658     QCOMPARE(columnsInsertedCounter, 1); // undo()
1659     QCOMPARE(columnsRemovedCounter, 2); // set and redo()
1660     QCOMPARE(columnsAboutToBeRemovedCounter, 2); // set and redo()
1661 }
1662 
1663 /*!
1664  * \brief testInsertRowsSuppressUpdate
1665  * It shall not crash
1666  * Testing if in the model begin and end are used properly
1667  */
1668 void SpreadsheetTest::testInsertRowsSuppressUpdate() {
1669     Project project;
1670     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1671     project.addChild(sheet);
1672 
1673     auto* model = new SpreadsheetModel(sheet);
1674 
1675     int rowsAboutToBeInsertedCounter = 0;
1676     connect(model, &SpreadsheetModel::rowsAboutToBeInserted, [this, &rowsAboutToBeInsertedCounter]() {
1677         rowsAboutToBeInsertedCounter++;
1678     });
1679     int rowsInsertedCounter = 0;
1680     connect(model, &SpreadsheetModel::rowsInserted, [this, &rowsInsertedCounter]() {
1681         rowsInsertedCounter++;
1682     });
1683     int rowsAboutToBeRemovedCounter = 0;
1684     connect(model, &SpreadsheetModel::rowsAboutToBeRemoved, [this, &rowsAboutToBeRemovedCounter]() {
1685         rowsAboutToBeRemovedCounter++;
1686     });
1687     int rowsRemovedCounter = 0;
1688     connect(model, &SpreadsheetModel::rowsRemoved, [this, &rowsRemovedCounter]() {
1689         rowsRemovedCounter++;
1690     });
1691 
1692     int modelResetCounter = 0;
1693     connect(model, &SpreadsheetModel::modelReset, [this, &modelResetCounter]() {
1694         modelResetCounter++;
1695     });
1696     int modelAboutToResetCounter = 0;
1697     connect(model, &SpreadsheetModel::modelAboutToBeReset, [this, &modelAboutToResetCounter]() {
1698         modelAboutToResetCounter++;
1699     });
1700 
1701     model->suppressSignals(true);
1702 
1703     QCOMPARE(sheet->rowCount(), 100);
1704     sheet->setRowCount(101); // No crash shall happen
1705     QCOMPARE(sheet->rowCount(), 101);
1706 
1707     sheet->undoStack()->undo();
1708     QCOMPARE(sheet->rowCount(), 100);
1709     sheet->undoStack()->redo();
1710     QCOMPARE(sheet->rowCount(), 101);
1711 
1712     model->suppressSignals(false);
1713 
1714     QCOMPARE(rowsAboutToBeInsertedCounter, 0);
1715     QCOMPARE(rowsInsertedCounter, 0);
1716     QCOMPARE(rowsAboutToBeRemovedCounter, 0);
1717     QCOMPARE(rowsRemovedCounter, 0);
1718     QCOMPARE(modelResetCounter, 1);
1719     QCOMPARE(modelAboutToResetCounter, 1);
1720 }
1721 
1722 /*!
1723  * \brief testInsertColumnsSuppressUpdate
1724  * It shall not crash
1725  * Testing if in the model begin and end are used properly
1726  */
1727 void SpreadsheetTest::testInsertColumnsSuppressUpdate() {
1728     Project project;
1729     auto* sheet = new Spreadsheet(QStringLiteral("test"), false);
1730     project.addChild(sheet);
1731 
1732     auto* model = new SpreadsheetModel(sheet);
1733 
1734     int columnsAboutToBeInsertedCounter = 0;
1735     connect(model, &SpreadsheetModel::columnsAboutToBeInserted, [this, &columnsAboutToBeInsertedCounter]() {
1736         columnsAboutToBeInsertedCounter++;
1737     });
1738     int columnsInsertedCounter = 0;
1739     connect(model, &SpreadsheetModel::columnsInserted, [this, &columnsInsertedCounter]() {
1740         columnsInsertedCounter++;
1741     });
1742     int columnsAboutToBeRemovedCounter = 0;
1743     connect(model, &SpreadsheetModel::columnsAboutToBeRemoved, [this, &columnsAboutToBeRemovedCounter]() {
1744         columnsAboutToBeRemovedCounter++;
1745     });
1746     int columnsRemovedCounter = 0;
1747     connect(model, &SpreadsheetModel::columnsRemoved, [this, &columnsRemovedCounter]() {
1748         columnsRemovedCounter++;
1749     });
1750 
1751     int modelResetCounter = 0;
1752     connect(model, &SpreadsheetModel::modelReset, [this, &modelResetCounter]() {
1753         modelResetCounter++;
1754     });
1755     int modelAboutToResetCounter = 0;
1756     connect(model, &SpreadsheetModel::modelAboutToBeReset, [this, &modelAboutToResetCounter]() {
1757         modelAboutToResetCounter++;
1758     });
1759 
1760     model->suppressSignals(true);
1761 
1762     QCOMPARE(sheet->columnCount(), 2);
1763     sheet->setColumnCount(5); // No crash shall happen
1764     QCOMPARE(sheet->columnCount(), 5);
1765 
1766     sheet->undoStack()->undo();
1767     QCOMPARE(sheet->columnCount(), 2);
1768     sheet->undoStack()->redo();
1769     QCOMPARE(sheet->columnCount(), 5);
1770 
1771     model->suppressSignals(false);
1772 
1773     QCOMPARE(columnsAboutToBeInsertedCounter, 0);
1774     QCOMPARE(columnsInsertedCounter, 0);
1775     QCOMPARE(columnsRemovedCounter, 0);
1776     QCOMPARE(columnsAboutToBeRemovedCounter, 0);
1777     QCOMPARE(modelResetCounter, 1);
1778     QCOMPARE(modelAboutToResetCounter, 1);
1779 }
1780 
1781 void SpreadsheetTest::testLinkSpreadsheetsUndoRedo() {
1782     Project project;
1783     auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
1784     project.addChild(sheetData);
1785     sheetData->setColumnCount(3);
1786     sheetData->setRowCount(10);
1787 
1788     auto* sheetData2 = new Spreadsheet(QStringLiteral("data2"), false);
1789     project.addChild(sheetData2);
1790     sheetData2->setColumnCount(3);
1791     sheetData2->setRowCount(100);
1792 
1793     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
1794     project.addChild(sheetCalculations);
1795     sheetCalculations->setColumnCount(3);
1796     sheetCalculations->setRowCount(2);
1797 
1798     SpreadsheetDock dock(nullptr);
1799     dock.setSpreadsheets({sheetCalculations});
1800 
1801     QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), false);
1802     QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), false);
1803     QCOMPARE(dock.ui.sbRowCount->isEnabled(), true);
1804     QCOMPARE(dock.m_spreadsheet->linking(), false);
1805 
1806     dock.ui.cbLinkingEnabled->toggled(true);
1807 
1808     // QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), true); // does not work here. Don't know why
1809     // QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), true);
1810     // QCOMPARE(dock.ui.sbRowCount->isEnabled(), false);
1811     QCOMPARE(sheetCalculations->linking(), true);
1812     QCOMPARE(sheetCalculations->linkedSpreadsheet(), nullptr);
1813     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), QLatin1String());
1814     QCOMPARE(sheetCalculations->rowCount(), 2);
1815 
1816     const auto index = dock.m_aspectTreeModel->modelIndexOfAspect(sheetData);
1817     QCOMPARE(index.isValid(), true);
1818     // dock.ui.cbLinkedSpreadsheet->setCurrentModelIndex(index); // Does not trigger the slot
1819     sheetCalculations->setLinkedSpreadsheet(sheetData);
1820 
1821     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
1822     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
1823     QCOMPARE(sheetCalculations->rowCount(), 10);
1824 
1825     sheetCalculations->setLinkedSpreadsheet(sheetData2);
1826 
1827     QCOMPARE(sheetCalculations->linking(), true);
1828     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData2);
1829     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData2->path());
1830     QCOMPARE(sheetCalculations->rowCount(), 100);
1831 
1832     sheetCalculations->undoStack()->undo();
1833 
1834     QCOMPARE(sheetCalculations->linking(), true);
1835     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
1836     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
1837     QCOMPARE(sheetCalculations->rowCount(), 10);
1838 
1839     sheetCalculations->undoStack()->redo();
1840 
1841     QCOMPARE(sheetCalculations->linking(), true);
1842     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData2);
1843     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData2->path());
1844     QCOMPARE(sheetCalculations->rowCount(), 100);
1845 
1846     sheetCalculations->undoStack()->undo(); // first undo
1847     QCOMPARE(sheetCalculations->linking(), true);
1848     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
1849     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
1850     QCOMPARE(sheetCalculations->rowCount(), 10);
1851 
1852     sheetCalculations->undoStack()->undo();
1853 
1854     QCOMPARE(sheetCalculations->linking(), true);
1855     QCOMPARE(sheetCalculations->linkedSpreadsheet(), nullptr); // No linked spreadsheet anymore
1856     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), QLatin1String());
1857     QCOMPARE(sheetCalculations->rowCount(), 2); // Go back to original row count
1858 
1859     sheetCalculations->undoStack()->undo();
1860     QCOMPARE(sheetCalculations->linking(), false);
1861     QCOMPARE(sheetCalculations->linkedSpreadsheet(), nullptr);
1862     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), QLatin1String());
1863     QCOMPARE(sheetCalculations->rowCount(), 2);
1864 }
1865 
1866 void SpreadsheetTest::testLinkSpreadsheetDeleteAdd() {
1867     Project project;
1868     auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
1869     project.addChild(sheetData);
1870     sheetData->setColumnCount(3);
1871     sheetData->setRowCount(10);
1872 
1873     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
1874     project.addChild(sheetCalculations);
1875     sheetCalculations->setColumnCount(3);
1876     sheetCalculations->setRowCount(2);
1877 
1878     SpreadsheetDock dock(nullptr);
1879     dock.setSpreadsheets({sheetCalculations});
1880 
1881     QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), false);
1882     QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), false);
1883     QCOMPARE(dock.ui.sbRowCount->isEnabled(), true);
1884     QCOMPARE(sheetCalculations->linking(), false);
1885 
1886     Q_EMIT dock.ui.cbLinkingEnabled->toggled(true);
1887 
1888     sheetCalculations->setLinkedSpreadsheet(sheetData);
1889 
1890     QCOMPARE(sheetCalculations->linking(), true);
1891     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
1892     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
1893     QCOMPARE(sheetCalculations->rowCount(), 10);
1894 
1895     sheetData->remove();
1896     sheetData->setRowCount(100);
1897 
1898     QCOMPARE(sheetCalculations->linking(), true);
1899     QCOMPARE(sheetCalculations->linkedSpreadsheet(), nullptr);
1900     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), QStringLiteral("Project/data"));
1901     QCOMPARE(sheetCalculations->rowCount(), 10); // does not change
1902 
1903     auto* sheetDataNew = new Spreadsheet(QStringLiteral("data"), false);
1904     sheetDataNew->setColumnCount(3);
1905     sheetDataNew->setRowCount(12);
1906     project.addChild(sheetDataNew);
1907 
1908     QCOMPARE(sheetCalculations->linking(), true);
1909     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetDataNew);
1910     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetDataNew->path());
1911     QCOMPARE(sheetCalculations->rowCount(), 12);
1912 }
1913 
1914 void SpreadsheetTest::testLinkSpreadsheetAddRow() {
1915     Project project;
1916     auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
1917     project.addChild(sheetData);
1918     sheetData->setColumnCount(3);
1919     sheetData->setRowCount(10);
1920 
1921     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
1922     project.addChild(sheetCalculations);
1923     sheetCalculations->setColumnCount(3);
1924     sheetCalculations->setRowCount(2);
1925 
1926     SpreadsheetDock dock(nullptr);
1927     dock.setSpreadsheets({sheetCalculations});
1928 
1929     QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), false);
1930     QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), false);
1931     QCOMPARE(dock.ui.sbRowCount->isEnabled(), true);
1932     QCOMPARE(sheetCalculations->linking(), false);
1933 
1934     Q_EMIT dock.ui.cbLinkingEnabled->toggled(true);
1935 
1936     sheetCalculations->setLinkedSpreadsheet(sheetData);
1937 
1938     QCOMPARE(sheetCalculations->linking(), true);
1939     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
1940     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
1941     QCOMPARE(sheetCalculations->rowCount(), 10);
1942 
1943     new SpreadsheetModel(sheetData); // otherwise emitRowCountChanged will not be called
1944     sheetData->setRowCount(13);
1945 
1946     QCOMPARE(sheetCalculations->linking(), true);
1947     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
1948     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
1949     QCOMPARE(sheetCalculations->rowCount(), 13);
1950 }
1951 
1952 void SpreadsheetTest::testLinkSpreadsheetRemoveRow() {
1953     Project project;
1954     auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
1955     project.addChild(sheetData);
1956     sheetData->setColumnCount(3);
1957     sheetData->setRowCount(10);
1958 
1959     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
1960     project.addChild(sheetCalculations);
1961     sheetCalculations->setColumnCount(3);
1962     sheetCalculations->setRowCount(2);
1963 
1964     SpreadsheetDock dock(nullptr);
1965     dock.setSpreadsheets({sheetCalculations});
1966 
1967     QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), false);
1968     QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), false);
1969     QCOMPARE(dock.ui.sbRowCount->isEnabled(), true);
1970     QCOMPARE(sheetCalculations->linking(), false);
1971 
1972     dock.ui.cbLinkingEnabled->toggled(true);
1973 
1974     sheetCalculations->setLinkedSpreadsheet(sheetData);
1975 
1976     QCOMPARE(sheetCalculations->linking(), true);
1977     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
1978     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
1979     QCOMPARE(sheetCalculations->rowCount(), 10);
1980 
1981     new SpreadsheetModel(sheetData); // otherwise emitRowCountChanged will not be called
1982     sheetData->setRowCount(7);
1983 
1984     QCOMPARE(sheetCalculations->linking(), true);
1985     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
1986     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
1987     QCOMPARE(sheetCalculations->rowCount(), 7);
1988 }
1989 
1990 void SpreadsheetTest::testLinkSpreadsheetRecalculate() {
1991     Project project;
1992     auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
1993     project.addChild(sheetData);
1994     sheetData->setColumnCount(2);
1995     sheetData->setRowCount(10);
1996     auto* sheetDataColumn0 = sheetData->child<Column>(0);
1997     sheetDataColumn0->replaceValues(0, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
1998     QVERIFY(sheetDataColumn0);
1999     auto* sheetDataColumn1 = sheetData->child<Column>(1);
2000     QVERIFY(sheetDataColumn1);
2001     sheetDataColumn1->replaceValues(0, {1, 2, 1, 2, 1, 2, 1, 2, 1, 3});
2002 
2003     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
2004     project.addChild(sheetCalculations);
2005     sheetCalculations->setColumnCount(1);
2006     sheetCalculations->setRowCount(2);
2007     auto* sheetCalculationsColumn0 = sheetCalculations->child<Column>(0);
2008     QVERIFY(sheetCalculationsColumn0);
2009     sheetCalculationsColumn0->setFormula(QStringLiteral("x + y"), {QStringLiteral("x"), QStringLiteral("y")}, {sheetDataColumn0, sheetDataColumn1}, true);
2010     sheetCalculationsColumn0->updateFormula();
2011 
2012     {
2013         QVector<double> ref{2, 4, 4, 6, 6, 8, 8, 10, 10, 13};
2014         QCOMPARE(sheetCalculationsColumn0->rowCount(), 10); // currently the update() triggers a resize
2015         for (int i = 0; i < 10; i++)
2016             VALUES_EQUAL(sheetCalculationsColumn0->doubleAt(i), ref.at(i));
2017     }
2018     sheetCalculations->setLinking(true);
2019     sheetCalculations->setLinkedSpreadsheet(sheetData);
2020 
2021     QCOMPARE(sheetCalculations->linking(), true);
2022     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2023     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2024     QCOMPARE(sheetCalculations->rowCount(), 10);
2025 
2026     new SpreadsheetModel(sheetData); // otherwise emitRowCountChanged will not be called
2027     sheetData->setRowCount(7);
2028     sheetDataColumn0->replaceValues(0, {3, 4, 6, 2, 1, 8, 5});
2029     QCOMPARE(sheetDataColumn0->rowCount(), 7);
2030 
2031     {
2032         QVector<double> ref{4, 6, 7, 4, 2, 10, 6};
2033         QCOMPARE(sheetCalculationsColumn0->rowCount(), ref.count());
2034         for (int i = 0; i < ref.count(); i++) {
2035             qDebug() << i;
2036             VALUES_EQUAL(sheetCalculationsColumn0->doubleAt(i), ref.at(i));
2037         }
2038     }
2039 }
2040 
2041 void SpreadsheetTest::testLinkSpreadsheetSaveLoad() {
2042     QString savePath;
2043     {
2044         Project project;
2045         auto model = new AspectTreeModel(&project, this);
2046         ProjectExplorer pe; // Needed otherwise the state key is missing in the file and then no restorePointers will be called
2047         pe.setProject(&project);
2048         pe.setModel(model);
2049         auto* sheetData = new Spreadsheet(QStringLiteral("data"), false);
2050         project.addChild(sheetData);
2051         sheetData->setColumnCount(3);
2052         sheetData->setRowCount(10);
2053 
2054         auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
2055         project.addChild(sheetCalculations);
2056         sheetCalculations->setColumnCount(3);
2057         sheetCalculations->setRowCount(2);
2058 
2059         SpreadsheetDock dock(nullptr);
2060         dock.setSpreadsheets({sheetCalculations});
2061 
2062         QCOMPARE(dock.ui.cbLinkingEnabled->isChecked(), false);
2063         QCOMPARE(dock.ui.cbLinkedSpreadsheet->isVisible(), false);
2064         QCOMPARE(dock.ui.sbRowCount->isEnabled(), true);
2065         QCOMPARE(sheetCalculations->linking(), false);
2066 
2067         Q_EMIT dock.ui.cbLinkingEnabled->toggled(true);
2068 
2069         sheetCalculations->setLinkedSpreadsheet(sheetData);
2070 
2071         QCOMPARE(sheetCalculations->linking(), true);
2072         QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2073         QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2074         QCOMPARE(sheetCalculations->rowCount(), 10);
2075 
2076         SAVE_PROJECT("testLinkSpreadsheetSaveLoad")
2077     }
2078 
2079     {
2080         Project project;
2081         QCOMPARE(project.load(savePath), true);
2082 
2083         auto sheetData = project.child<Spreadsheet>(0);
2084         QVERIFY(sheetData);
2085         auto sheetCalculations = project.child<Spreadsheet>(1);
2086         QVERIFY(sheetCalculations);
2087 
2088         QCOMPARE(sheetCalculations->linking(), true);
2089         QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2090         QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2091         QCOMPARE(sheetCalculations->rowCount(), 10);
2092 
2093         new SpreadsheetModel(sheetData); // otherwise emitRowCountChanged will not be called
2094         sheetData->setRowCount(11); // Changing shall also update sheetCalculations also after loading
2095 
2096         QCOMPARE(sheetCalculations->linking(), true);
2097         QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2098         QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2099         QCOMPARE(sheetCalculations->rowCount(), 11);
2100     }
2101 }
2102 
2103 #ifdef HAVE_VECTOR_BLF
2104 // Copied from BLFFilterTest.cpp
2105 namespace {
2106 static const std::string PRIMITIVE_DBC =
2107     R"(VERSION "1.0.0"
2108 
2109 NS_ :
2110 
2111 BS_:
2112 
2113 BU_: DBG DRIVER IO MOTOR SENSOR
2114 
2115 )";
2116 
2117 void createDBCFile(const QString& filename, const std::string& content) {
2118     auto* file = std::fopen(filename.toStdString().c_str(), "w");
2119     QVERIFY(file);
2120     std::fputs(PRIMITIVE_DBC.c_str(), file);
2121     std::fputs(content.c_str(), file);
2122     std::fclose(file);
2123 }
2124 
2125 Vector::BLF::CanMessage2* createCANMessage(uint32_t id, uint64_t timestamp, const std::vector<uint8_t>& data) {
2126     auto* canMessage = new Vector::BLF::CanMessage2();
2127     canMessage->channel = 1;
2128     canMessage->flags = 1; // TX
2129     canMessage->dlc = std::min<uint8_t>(data.size(), 8);
2130     canMessage->id = id;
2131     canMessage->objectTimeStamp = timestamp;
2132     canMessage->objectFlags = Vector::BLF::ObjectHeader::ObjectFlags::TimeOneNans;
2133     if (canMessage->data.size() < canMessage->dlc)
2134         canMessage->data.resize(canMessage->dlc);
2135 
2136     for (int i = 0; i < canMessage->dlc; i++) {
2137         canMessage->data[i] = data.at(i);
2138     }
2139     return canMessage;
2140 }
2141 
2142 void createBLFFile(const QString& filename, QVector<Vector::BLF::CanMessage2*> messages) {
2143     Vector::BLF::File blfFile;
2144     blfFile.open(filename.toStdString().c_str(), std::ios_base::out);
2145     QVERIFY(blfFile.is_open());
2146 
2147     for (auto msg : messages) {
2148         blfFile.write(msg);
2149     }
2150     // Finish creating files
2151     blfFile.close();
2152 }
2153 }
2154 
2155 void SpreadsheetTest::testLinkSpreadSheetImportBLF() {
2156     QTemporaryFile blfFileName(QStringLiteral("XXXXXX.blf"));
2157     QVERIFY(blfFileName.open());
2158     QVector<Vector::BLF::CanMessage2*> messages{
2159         createCANMessage(337, 5, {0, 4, 252, 19, 0, 0, 0, 0}),
2160         createCANMessage(541, 10, {7, 39, 118, 33, 250, 30, 76, 24}), // 99.91, 85.66, 79.3, 22.2
2161         createCANMessage(337, 15, {47, 4, 60, 29, 0, 0, 0, 0}),
2162         createCANMessage(337, 20, {57, 4, 250, 29, 0, 0, 0, 0}),
2163         createCANMessage(541, 25, {7, 39, 118, 33, 250, 30, 76, 24}), // 99.91, 85.66, 79.3, 22.2
2164     }; // time is in nanoseconds
2165     createBLFFile(blfFileName.fileName(), messages);
2166 
2167     QTemporaryFile dbcFile(QStringLiteral("XXXXXX.dbc"));
2168     QVERIFY(dbcFile.open());
2169     const auto dbcContent = R"(BO_ 337 STATUS: 8 Vector__XXX
2170  SG_ Value6 : 27|3@1+ (1,0) [0|7] ""  Vector__XXX
2171  SG_ Value5 : 16|11@1+ (0.1,-102) [-102|102] "%"  Vector__XXX
2172  SG_ Value2 : 8|2@1+ (1,0) [0|2] ""  Vector__XXX
2173  SG_ Value3 : 10|1@1+ (1,0) [0|1] ""  Vector__XXX
2174  SG_ Value7 : 30|2@1+ (1,0) [0|3] ""  Vector__XXX
2175  SG_ Value4 : 11|4@1+ (1,0) [0|3] ""  Vector__XXX
2176  SG_ Value1 : 0|8@1+ (1,0) [0|204] "Km/h"  Vector__XXX"
2177 BO_ 541 MSG2: 8 Vector__XXX
2178  SG_ MSG2Value4 : 48|16@1+ (0.01,-40) [-40|125] "C"  Vector__XXX
2179  SG_ MSG2Value1 : 0|16@1+ (0.01,0) [0|100] "%"  Vector__XXX
2180  SG_ MSG2Value3 : 32|16@1+ (0.01,0) [0|100] "%"  Vector__XXX
2181  SG_ MSG2Value2 : 16|16@1+ (0.01,0) [0|100] "%"  Vector__XXX
2182 )";
2183     createDBCFile(dbcFile.fileName(), dbcContent);
2184 
2185     //------------------------------------------------------------------------------------------
2186     Project project;
2187     const auto spreadsheetName = blfFileName.fileName().replace(QStringLiteral(".blf"), QStringLiteral(""));
2188     auto* sheetData = new Spreadsheet(spreadsheetName, false);
2189     project.addChild(sheetData);
2190     sheetData->setColumnCount(2);
2191     sheetData->setRowCount(10);
2192     auto* sheetDataColumn0 = sheetData->child<Column>(0);
2193     sheetDataColumn0->setName(QStringLiteral("Value6_"));
2194     sheetDataColumn0->replaceValues(0, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
2195     QVERIFY(sheetDataColumn0);
2196     auto* sheetDataColumn1 = sheetData->child<Column>(1);
2197     sheetDataColumn1->setName(QStringLiteral("Value5_%"));
2198     QVERIFY(sheetDataColumn1);
2199     sheetDataColumn1->replaceValues(0, {1, 2, 1, 2, 1, 2, 1, 2, 1, 3});
2200 
2201     auto* sheetCalculations = new Spreadsheet(QStringLiteral("calculations"), false);
2202     project.addChild(sheetCalculations);
2203     sheetCalculations->setColumnCount(2);
2204     sheetCalculations->setRowCount(2);
2205 
2206     auto* sheetCalculationsColumn0 = sheetCalculations->child<Column>(0);
2207     QVERIFY(sheetCalculationsColumn0);
2208     sheetCalculationsColumn0->setFormula(QStringLiteral("2*x"), {QStringLiteral("x")}, {sheetDataColumn0}, true);
2209     sheetCalculationsColumn0->updateFormula();
2210 
2211     auto* sheetCalculationsColumn1 = sheetCalculations->child<Column>(1);
2212     QVERIFY(sheetCalculationsColumn1);
2213     sheetCalculationsColumn1->setFormula(QStringLiteral("2*x"), {QStringLiteral("x")}, {sheetDataColumn1}, true);
2214     sheetCalculationsColumn1->updateFormula();
2215 
2216     {
2217         QVector<double> ref{2, 4, 6, 8, 10, 12, 14, 16, 18, 20};
2218         QCOMPARE(sheetCalculationsColumn0->rowCount(), 10);
2219         for (int i = 0; i < 10; i++)
2220             VALUES_EQUAL(sheetCalculationsColumn0->doubleAt(i), ref.at(i));
2221     }
2222 
2223     {
2224         QVector<double> ref{2, 4, 2, 4, 2, 4, 2, 4, 2, 6};
2225         QCOMPARE(sheetCalculationsColumn1->rowCount(), 10);
2226         for (int i = 0; i < 10; i++)
2227             VALUES_EQUAL(sheetCalculationsColumn1->doubleAt(i), ref.at(i));
2228     }
2229 
2230     sheetCalculations->setLinking(true);
2231     sheetCalculations->setLinkedSpreadsheet(sheetData);
2232 
2233     QCOMPARE(sheetCalculations->linking(), true);
2234     QCOMPARE(sheetCalculations->linkedSpreadsheet(), sheetData);
2235     QCOMPARE(sheetCalculations->linkedSpreadsheetPath(), sheetData->path());
2236     QCOMPARE(sheetCalculations->rowCount(), 10);
2237 
2238     new SpreadsheetModel(sheetData); // otherwise emitRowCountChanged will not be called
2239 
2240     VectorBLFFilter filter;
2241     filter.setConvertTimeToSeconds(true);
2242     filter.setTimeHandlingMode(CANFilter::TimeHandling::ConcatPrevious);
2243     QCOMPARE(filter.isValid(blfFileName.fileName()), true);
2244 
2245     // Valid blf and valid dbc
2246     filter.setDBCFile(dbcFile.fileName());
2247     filter.readDataFromFile(blfFileName.fileName(), sheetData);
2248     QCOMPARE(sheetData->columnCount(), 12);
2249 
2250     QCOMPARE(sheetData->rowCount(), 5);
2251     QCOMPARE(sheetDataColumn0->rowCount(), 5);
2252     QCOMPARE(sheetDataColumn1->rowCount(), 5);
2253 
2254     const auto* sheetDataColumn6 = sheetData->child<Column>(1);
2255     QCOMPARE(sheetDataColumn6->name(), QStringLiteral("Value6_"));
2256     const auto* sheetDataColumn5 = sheetData->child<Column>(2);
2257     QCOMPARE(sheetDataColumn5->name(), QStringLiteral("Value5_%"));
2258 
2259     {
2260         QVector<double> ref{4., 4., 6., 6., 6.};
2261         const auto* sheetCalculationsColumn = sheetCalculations->child<Column>(0);
2262         QCOMPARE(sheetCalculationsColumn->formulaData().at(0).column(), sheetDataColumn6);
2263         QCOMPARE(sheetCalculationsColumn->rowCount(), ref.count());
2264         for (int i = 0; i < ref.count(); i++) {
2265             qDebug() << i;
2266             VALUES_EQUAL(sheetCalculationsColumn->doubleAt(i), ref.at(i));
2267         }
2268     }
2269 
2270     {
2271         QVector<double> ref{0., 0., 64., 102., 102.};
2272         const auto* sheetCalculationsColumn = sheetCalculations->child<Column>(1);
2273         QCOMPARE(sheetCalculationsColumn->formulaData().at(0).column(), sheetDataColumn5);
2274         QCOMPARE(sheetCalculationsColumn->rowCount(), ref.count());
2275         for (int i = 0; i < ref.count(); i++) {
2276             qDebug() << i;
2277             VALUES_EQUAL(sheetCalculationsColumn->doubleAt(i), ref.at(i));
2278         }
2279     }
2280 }
2281 #endif // HAVE_VECTOR_BLF
2282 
2283 QTEST_MAIN(SpreadsheetTest)