File indexing completed on 2024-04-14 03:46:35

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