File indexing completed on 2024-05-12 04:19:58
0001 /* 0002 Gwenview: an image viewer 0003 Copyright 2007 Aurélien Gâteau <agateau@kde.org> 0004 0005 This program is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU General Public License 0007 as published by the Free Software Foundation; either version 2 0008 of the License, or (at your option) any later version. 0009 0010 This program is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 GNU General Public License for more details. 0014 0015 You should have received a copy of the GNU General Public License 0016 along with this program; if not, write to the Free Software 0017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0018 0019 */ 0020 // Qt 0021 #include <QConicalGradient> 0022 #include <QImage> 0023 #include <QPainter> 0024 #include <QTest> 0025 0026 // KF 0027 #include <KIO/StatJob> 0028 #include <KJobUiDelegate> 0029 #include <kio_version.h> 0030 0031 // KDCraw 0032 #include <KDCRAW/KDcraw> 0033 0034 // Local 0035 #include "../lib/abstractimageoperation.h" 0036 #include "../lib/document/abstractdocumenteditor.h" 0037 #include "../lib/document/documentfactory.h" 0038 #include "../lib/document/documentjob.h" 0039 #include "../lib/imagemetainfomodel.h" 0040 #include "../lib/imageutils.h" 0041 #include "../lib/transformimageoperation.h" 0042 #include "testutils.h" 0043 0044 #include <exiv2/exif.hpp> 0045 0046 #include "documenttest.h" 0047 0048 QTEST_MAIN(DocumentTest) 0049 0050 using namespace Gwenview; 0051 0052 static void waitUntilMetaInfoLoaded(Document::Ptr doc) 0053 { 0054 while (doc->loadingState() < Document::MetaInfoLoaded) { 0055 QTest::qWait(100); 0056 } 0057 } 0058 0059 static bool waitUntilJobIsDone(DocumentJob *job) 0060 { 0061 JobWatcher watcher(job); 0062 watcher.wait(); 0063 return watcher.error() == KJob::NoError; 0064 } 0065 0066 void DocumentTest::initTestCase() 0067 { 0068 qRegisterMetaType<QUrl>("QUrl"); 0069 } 0070 0071 void DocumentTest::init() 0072 { 0073 DocumentFactory::instance()->clearCache(); 0074 } 0075 0076 void DocumentTest::testLoad() 0077 { 0078 QFETCH(QString, fileName); 0079 QFETCH(QByteArray, expectedFormat); 0080 QFETCH(int, expectedKindInt); 0081 QFETCH(bool, expectedIsAnimated); 0082 QFETCH(QImage, expectedImage); 0083 QFETCH(int, maxHeight); // number of lines to test. -1 to test all lines 0084 0085 auto expectedKind = MimeTypeUtils::Kind(expectedKindInt); 0086 0087 QUrl url = urlForTestFile(fileName); 0088 0089 // testing RAW loading. For raw, QImage directly won't work -> load it using KDCRaw 0090 QByteArray mFormatHint = url.fileName().section('.', -1).toLocal8Bit().toLower(); 0091 if (KDcrawIface::KDcraw::rawFilesList().contains(QString(mFormatHint))) { 0092 if (!KDcrawIface::KDcraw::loadEmbeddedPreview(expectedImage, url.toLocalFile())) { 0093 QSKIP( 0094 "Not running this test: failed to get expectedImage. Try running ./fetch_testing_raw.sh\ 0095 in the tests/data directory and then rerun the tests."); 0096 } 0097 } 0098 0099 if (expectedKind != MimeTypeUtils::KIND_SVG_IMAGE) { 0100 if (expectedImage.isNull()) { 0101 QSKIP("Not running this test: QImage failed to load the test image"); 0102 } 0103 } 0104 0105 Document::Ptr doc = DocumentFactory::instance()->load(url); 0106 QSignalSpy spy(doc.data(), SIGNAL(isAnimatedUpdated())); 0107 doc->waitUntilLoaded(); 0108 QCOMPARE(doc->loadingState(), Document::Loaded); 0109 0110 QCOMPARE(doc->kind(), expectedKind); 0111 QCOMPARE(doc->isAnimated(), expectedIsAnimated); 0112 QCOMPARE(spy.count(), doc->isAnimated() ? 1 : 0); 0113 if (doc->kind() == MimeTypeUtils::KIND_RASTER_IMAGE) { 0114 QImage image = doc->image(); 0115 if (maxHeight > -1) { 0116 QRect poiRect(0, 0, image.width(), maxHeight); 0117 image = image.copy(poiRect); 0118 expectedImage = expectedImage.copy(poiRect); 0119 } 0120 QCOMPARE(image, expectedImage); 0121 QCOMPARE(QString(doc->format()), QString(expectedFormat)); 0122 } 0123 } 0124 0125 static void testLoad_newRow(const char *fileName, 0126 const QByteArray &format, 0127 MimeTypeUtils::Kind kind = MimeTypeUtils::KIND_RASTER_IMAGE, 0128 bool isAnimated = false, 0129 int maxHeight = -1) 0130 { 0131 QTest::newRow(fileName) << fileName << QByteArray(format) << int(kind) << isAnimated << QImage(pathForTestFile(fileName), format) << maxHeight; 0132 } 0133 0134 void DocumentTest::testLoad_data() 0135 { 0136 QTest::addColumn<QString>("fileName"); 0137 QTest::addColumn<QByteArray>("expectedFormat"); 0138 QTest::addColumn<int>("expectedKindInt"); 0139 QTest::addColumn<bool>("expectedIsAnimated"); 0140 QTest::addColumn<QImage>("expectedImage"); 0141 QTest::addColumn<int>("maxHeight"); 0142 0143 testLoad_newRow("test.png", "png"); 0144 testLoad_newRow("160216_no_size_before_decoding.eps", "eps"); 0145 testLoad_newRow("160382_corrupted.jpeg", "jpeg", MimeTypeUtils::KIND_RASTER_IMAGE, false, 55); 0146 testLoad_newRow("1x10k.png", "png"); 0147 testLoad_newRow("1x10k.jpg", "jpeg"); 0148 testLoad_newRow("test.xcf", "xcf"); 0149 testLoad_newRow("188191_does_not_load.tga", "tga"); 0150 testLoad_newRow("289819_does_not_load.png", "png"); 0151 testLoad_newRow("png-with-jpeg-extension.jpg", "png"); 0152 testLoad_newRow("jpg-with-gif-extension.gif", "jpeg"); 0153 0154 // RAW preview 0155 testLoad_newRow("CANON-EOS350D-02.CR2", "cr2", MimeTypeUtils::KIND_RASTER_IMAGE, false); 0156 testLoad_newRow("dsc_0093.nef", "nef", MimeTypeUtils::KIND_RASTER_IMAGE, false); 0157 0158 // SVG 0159 testLoad_newRow("test.svg", "", MimeTypeUtils::KIND_SVG_IMAGE); 0160 // FIXME: Test svgz 0161 0162 // Animated 0163 testLoad_newRow("4frames.gif", "gif", MimeTypeUtils::KIND_RASTER_IMAGE, true); 0164 testLoad_newRow("1frame.gif", "gif", MimeTypeUtils::KIND_RASTER_IMAGE, false); 0165 testLoad_newRow("185523_1frame_with_graphic_control_extension.gif", "gif", MimeTypeUtils::KIND_RASTER_IMAGE, false); 0166 } 0167 0168 void DocumentTest::testLoadTwoPasses() 0169 { 0170 QUrl url = urlForTestFile("test.png"); 0171 QImage image; 0172 bool ok = image.load(url.toLocalFile()); 0173 QVERIFY2(ok, "Could not load 'test.png'"); 0174 Document::Ptr doc = DocumentFactory::instance()->load(url); 0175 waitUntilMetaInfoLoaded(doc); 0176 QVERIFY2(doc->image().isNull(), "Image shouldn't have been loaded at this time"); 0177 QCOMPARE(doc->format().data(), "png"); 0178 doc->waitUntilLoaded(); 0179 QCOMPARE(image, doc->image()); 0180 } 0181 0182 void DocumentTest::testLoadEmpty() 0183 { 0184 QUrl url = urlForTestFile("empty.png"); 0185 Document::Ptr doc = DocumentFactory::instance()->load(url); 0186 while (doc->loadingState() <= Document::KindDetermined) { 0187 QTest::qWait(100); 0188 } 0189 QCOMPARE(doc->loadingState(), Document::LoadingFailed); 0190 } 0191 0192 #define NEW_ROW(fileName) QTest::newRow(fileName) << fileName 0193 void DocumentTest::testLoadDownSampled_data() 0194 { 0195 QTest::addColumn<QString>("fileName"); 0196 0197 NEW_ROW("orient6.jpg"); 0198 NEW_ROW("1x10k.jpg"); 0199 } 0200 #undef NEW_ROW 0201 0202 void DocumentTest::testLoadDownSampled() 0203 { 0204 // Note: for now we only support down sampling on jpeg, do not use test.png 0205 // here 0206 QFETCH(QString, fileName); 0207 QUrl url = urlForTestFile(fileName); 0208 QImage image; 0209 bool ok = image.load(url.toLocalFile()); 0210 QVERIFY2(ok, "Could not load test image"); 0211 Document::Ptr doc = DocumentFactory::instance()->load(url); 0212 0213 QSignalSpy downSampledImageReadySpy(doc.data(), SIGNAL(downSampledImageReady())); 0214 QSignalSpy loadingFailedSpy(doc.data(), SIGNAL(loadingFailed(QUrl))); 0215 QSignalSpy loadedSpy(doc.data(), SIGNAL(loaded(QUrl))); 0216 bool ready = doc->prepareDownSampledImageForZoom(0.2); 0217 QVERIFY2(!ready, "There should not be a down sampled image at this point"); 0218 0219 while (downSampledImageReadySpy.isEmpty() && loadingFailedSpy.isEmpty() && loadedSpy.isEmpty()) { 0220 QTest::qWait(100); 0221 } 0222 QImage downSampledImage = doc->downSampledImageForZoom(0.2); 0223 QVERIFY2(!downSampledImage.isNull(), "Down sampled image should not be null"); 0224 0225 QSize expectedSize = doc->size() / 2; 0226 if (expectedSize.isEmpty()) { 0227 expectedSize = image.size(); 0228 } 0229 QCOMPARE(downSampledImage.size(), expectedSize); 0230 } 0231 0232 /** 0233 * Down sampling is not supported on png. We should get a complete image 0234 * instead. 0235 */ 0236 void DocumentTest::testLoadDownSampledPng() 0237 { 0238 QUrl url = urlForTestFile("test.png"); 0239 QImage image; 0240 bool ok = image.load(url.toLocalFile()); 0241 QVERIFY2(ok, "Could not load test image"); 0242 Document::Ptr doc = DocumentFactory::instance()->load(url); 0243 0244 LoadingStateSpy stateSpy(doc); 0245 connect(doc.data(), &Document::loaded, &stateSpy, &LoadingStateSpy::readState); 0246 0247 bool ready = doc->prepareDownSampledImageForZoom(0.2); 0248 QVERIFY2(!ready, "There should not be a down sampled image at this point"); 0249 0250 doc->waitUntilLoaded(); 0251 0252 QCOMPARE(stateSpy.mCallCount, 1); 0253 QCOMPARE(stateSpy.mState, Document::Loaded); 0254 } 0255 0256 void DocumentTest::testLoadRemote() 0257 { 0258 QUrl url = setUpRemoteTestDir("test.png"); 0259 if (!url.isValid()) { 0260 QSKIP("Not running this test: failed to setup remote test dir."); 0261 } 0262 url = url.adjusted(QUrl::StripTrailingSlash); 0263 url.setPath(url.path() + '/' + "test.png"); 0264 0265 QVERIFY2(KIO::stat(url, KIO::StatJob::SourceSide, KIO::StatNoDetails)->exec(), "test url not found"); 0266 0267 Document::Ptr doc = DocumentFactory::instance()->load(url); 0268 doc->waitUntilLoaded(); 0269 QImage image = doc->image(); 0270 QCOMPARE(image.width(), 150); 0271 QCOMPARE(image.height(), 100); 0272 } 0273 0274 void DocumentTest::testLoadAnimated() 0275 { 0276 QUrl srcUrl = urlForTestFile("40frames.gif"); 0277 Document::Ptr doc = DocumentFactory::instance()->load(srcUrl); 0278 QSignalSpy spy(doc.data(), SIGNAL(imageRectUpdated(QRect))); 0279 doc->waitUntilLoaded(); 0280 QVERIFY(doc->isAnimated()); 0281 0282 // Test we receive only one imageRectUpdated() until animation is started 0283 // (the imageRectUpdated() is triggered by the loading of the first image) 0284 QTest::qWait(1000); 0285 QCOMPARE(spy.count(), 1); 0286 0287 // Test we now receive some imageRectUpdated() 0288 doc->startAnimation(); 0289 QTest::qWait(1000); 0290 int count = spy.count(); 0291 doc->stopAnimation(); 0292 QVERIFY2(count > 0, "No imageRectUpdated() signal received"); 0293 0294 // Test we do not receive imageRectUpdated() anymore 0295 QTest::qWait(1000); 0296 QCOMPARE(count, spy.count()); 0297 0298 // Start again, we should receive imageRectUpdated() again 0299 doc->startAnimation(); 0300 QTest::qWait(1000); 0301 QVERIFY2(spy.count() > count, "No imageRectUpdated() signal received after restarting"); 0302 } 0303 0304 void DocumentTest::testPrepareDownSampledAfterFailure() 0305 { 0306 QUrl url = urlForTestFile("empty.png"); 0307 Document::Ptr doc = DocumentFactory::instance()->load(url); 0308 0309 doc->waitUntilLoaded(); 0310 QCOMPARE(doc->loadingState(), Document::LoadingFailed); 0311 0312 bool ready = doc->prepareDownSampledImageForZoom(0.25); 0313 QVERIFY2(!ready, "Down sampled image should not be ready"); 0314 } 0315 0316 void DocumentTest::testSaveRemote() 0317 { 0318 QUrl dstUrl = setUpRemoteTestDir(); 0319 if (!dstUrl.isValid()) { 0320 QSKIP("Not running this test: failed to setup remote test dir."); 0321 } 0322 0323 QUrl srcUrl = urlForTestFile("test.png"); 0324 Document::Ptr doc = DocumentFactory::instance()->load(srcUrl); 0325 doc->waitUntilLoaded(); 0326 0327 dstUrl = dstUrl.adjusted(QUrl::StripTrailingSlash); 0328 dstUrl.setPath(dstUrl.path() + '/' + "testSaveRemote.png"); 0329 QVERIFY(waitUntilJobIsDone(doc->save(dstUrl, "png"))); 0330 } 0331 0332 /** 0333 * Check that deleting a document while it is loading does not crash 0334 */ 0335 void DocumentTest::testDeleteWhileLoading() 0336 { 0337 { 0338 QUrl url = urlForTestFile("test.png"); 0339 QImage image; 0340 bool ok = image.load(url.toLocalFile()); 0341 QVERIFY2(ok, "Could not load 'test.png'"); 0342 Document::Ptr doc = DocumentFactory::instance()->load(url); 0343 } 0344 DocumentFactory::instance()->clearCache(); 0345 // Wait two seconds. If the test fails we will get a segfault while waiting 0346 QTest::qWait(2000); 0347 } 0348 0349 void DocumentTest::testLoadRotated() 0350 { 0351 QUrl url = urlForTestFile("orient6.jpg"); 0352 QImage image; 0353 bool ok = image.load(url.toLocalFile()); 0354 QVERIFY2(ok, "Could not load 'orient6.jpg'"); 0355 QTransform matrix = ImageUtils::transformMatrix(ROT_90); 0356 image = image.transformed(matrix); 0357 0358 Document::Ptr doc = DocumentFactory::instance()->load(url); 0359 doc->waitUntilLoaded(); 0360 QCOMPARE(image, doc->image()); 0361 0362 // RAW preview on rotated image 0363 url = urlForTestFile("dsd_1838.nef"); 0364 if (!KDcrawIface::KDcraw::loadEmbeddedPreview(image, url.toLocalFile())) { 0365 QSKIP( 0366 "Not running this test: failed to get image. Try running ./fetch_testing_raw.sh\ 0367 in the tests/data directory and then rerun the tests."); 0368 } 0369 matrix = ImageUtils::transformMatrix(ROT_270); 0370 image = image.transformed(matrix); 0371 0372 doc = DocumentFactory::instance()->load(url); 0373 doc->waitUntilLoaded(); 0374 QCOMPARE(image, doc->image()); 0375 } 0376 0377 /** 0378 * Checks that asking the DocumentFactory the same document twice in a row does 0379 * not load it twice 0380 */ 0381 void DocumentTest::testMultipleLoads() 0382 { 0383 QUrl url = urlForTestFile("orient6.jpg"); 0384 Document::Ptr doc1 = DocumentFactory::instance()->load(url); 0385 Document::Ptr doc2 = DocumentFactory::instance()->load(url); 0386 0387 QCOMPARE(doc1.data(), doc2.data()); 0388 } 0389 0390 void DocumentTest::testSaveAs() 0391 { 0392 QUrl url = urlForTestFile("orient6.jpg"); 0393 DocumentFactory *factory = DocumentFactory::instance(); 0394 Document::Ptr doc = factory->load(url); 0395 QSignalSpy savedSpy(doc.data(), SIGNAL(saved(QUrl, QUrl))); 0396 QSignalSpy modifiedDocumentListChangedSpy(factory, SIGNAL(modifiedDocumentListChanged())); 0397 QSignalSpy documentChangedSpy(factory, SIGNAL(documentChanged(QUrl))); 0398 doc->startLoadingFullImage(); 0399 0400 QUrl destUrl = urlForTestOutputFile("result.png"); 0401 QVERIFY(waitUntilJobIsDone(doc->save(destUrl, "png"))); 0402 QCOMPARE(doc->format().data(), "png"); 0403 QCOMPARE(doc->url(), destUrl); 0404 QCOMPARE(doc->metaInfo()->getValueForKey("General.Name"), destUrl.fileName()); 0405 0406 QVERIFY2(doc->loadingState() == Document::Loaded, "Document is supposed to finish loading before saving"); 0407 0408 QTest::qWait(100); // saved() is emitted asynchronously 0409 QCOMPARE(savedSpy.count(), 1); 0410 QVariantList args = savedSpy.takeFirst(); 0411 QCOMPARE(args.at(0).toUrl(), url); 0412 QCOMPARE(args.at(1).toUrl(), destUrl); 0413 0414 QImage image("result.png", "png"); 0415 QCOMPARE(doc->image(), image); 0416 0417 QVERIFY(!DocumentFactory::instance()->hasUrl(url)); 0418 QVERIFY(DocumentFactory::instance()->hasUrl(destUrl)); 0419 0420 QCOMPARE(modifiedDocumentListChangedSpy.count(), 0); // No changes were made 0421 0422 QCOMPARE(documentChangedSpy.count(), 1); 0423 args = documentChangedSpy.takeFirst(); 0424 QCOMPARE(args.at(0).toUrl(), destUrl); 0425 } 0426 0427 void DocumentTest::testLosslessSave() 0428 { 0429 QUrl url1 = urlForTestFile("orient6.jpg"); 0430 Document::Ptr doc = DocumentFactory::instance()->load(url1); 0431 doc->startLoadingFullImage(); 0432 0433 QUrl url2 = urlForTestOutputFile("orient1.jpg"); 0434 QVERIFY(waitUntilJobIsDone(doc->save(url2, "jpeg"))); 0435 0436 QImage image1; 0437 QVERIFY(image1.load(url1.toLocalFile())); 0438 0439 QImage image2; 0440 QVERIFY(image2.load(url2.toLocalFile())); 0441 0442 QCOMPARE(image1, image2); 0443 } 0444 0445 void DocumentTest::testLosslessRotate() 0446 { 0447 // Generate test image 0448 QImage image1(200, 96, QImage::Format_RGB32); 0449 { 0450 QPainter painter(&image1); 0451 QConicalGradient gradient(QPointF(100, 48), 100); 0452 gradient.setColorAt(0, Qt::white); 0453 gradient.setColorAt(1, Qt::blue); 0454 painter.fillRect(image1.rect(), gradient); 0455 } 0456 0457 QUrl url1 = urlForTestOutputFile("lossless1.jpg"); 0458 QVERIFY(image1.save(url1.toLocalFile(), "jpeg")); 0459 0460 // Load it as a Gwenview document 0461 Document::Ptr doc = DocumentFactory::instance()->load(url1); 0462 doc->waitUntilLoaded(); 0463 0464 // Rotate one time 0465 QVERIFY(doc->editor()); 0466 doc->editor()->applyTransformation(ROT_90); 0467 0468 // Save it 0469 QUrl url2 = urlForTestOutputFile("lossless2.jpg"); 0470 waitUntilJobIsDone(doc->save(url2, "jpeg")); 0471 0472 // Load the saved image 0473 doc = DocumentFactory::instance()->load(url2); 0474 doc->waitUntilLoaded(); 0475 0476 // Rotate the other way 0477 QVERIFY(doc->editor()); 0478 doc->editor()->applyTransformation(ROT_270); 0479 waitUntilJobIsDone(doc->save(url2, "jpeg")); 0480 0481 // Compare the saved images 0482 QVERIFY(image1.load(url1.toLocalFile())); 0483 QImage image2; 0484 QVERIFY(image2.load(url2.toLocalFile())); 0485 0486 QCOMPARE(image1, image2); 0487 } 0488 0489 void DocumentTest::testModifyAndSaveAs() 0490 { 0491 QVariantList args; 0492 class TestOperation : public AbstractImageOperation 0493 { 0494 public: 0495 void redo() override 0496 { 0497 QImage image(10, 10, QImage::Format_ARGB32); 0498 image.fill(QColor(Qt::white).rgb()); 0499 document()->editor()->setImage(image); 0500 finish(true); 0501 } 0502 }; 0503 QUrl url = urlForTestFile("orient6.jpg"); 0504 DocumentFactory *factory = DocumentFactory::instance(); 0505 Document::Ptr doc = factory->load(url); 0506 0507 QSignalSpy savedSpy(doc.data(), SIGNAL(saved(QUrl, QUrl))); 0508 QSignalSpy modifiedDocumentListChangedSpy(factory, SIGNAL(modifiedDocumentListChanged())); 0509 QSignalSpy documentChangedSpy(factory, SIGNAL(documentChanged(QUrl))); 0510 0511 doc->waitUntilLoaded(); 0512 QVERIFY(!doc->isModified()); 0513 QCOMPARE(modifiedDocumentListChangedSpy.count(), 0); 0514 0515 // Modify image 0516 QVERIFY(doc->editor()); 0517 auto op = new TestOperation; 0518 op->applyToDocument(doc); 0519 QTest::qWait(100); 0520 QVERIFY(doc->isModified()); 0521 QCOMPARE(modifiedDocumentListChangedSpy.count(), 1); 0522 modifiedDocumentListChangedSpy.clear(); 0523 QList<QUrl> lst = factory->modifiedDocumentList(); 0524 QCOMPARE(lst.count(), 1); 0525 QCOMPARE(lst.first(), url); 0526 QCOMPARE(documentChangedSpy.count(), 1); 0527 args = documentChangedSpy.takeFirst(); 0528 QCOMPARE(args.at(0).toUrl(), url); 0529 0530 // Save it under a new name 0531 QUrl destUrl = urlForTestOutputFile("modify.png"); 0532 QVERIFY(waitUntilJobIsDone(doc->save(destUrl, "png"))); 0533 0534 // Wait a bit because save() will clear the undo stack when back to the 0535 // event loop 0536 QTest::qWait(100); 0537 QVERIFY(!doc->isModified()); 0538 0539 QVERIFY(!factory->hasUrl(url)); 0540 QVERIFY(factory->hasUrl(destUrl)); 0541 QCOMPARE(modifiedDocumentListChangedSpy.count(), 1); 0542 QVERIFY(DocumentFactory::instance()->modifiedDocumentList().isEmpty()); 0543 0544 QCOMPARE(documentChangedSpy.count(), 2); 0545 QList<QUrl> modifiedUrls = QList<QUrl>() << url << destUrl; 0546 QVERIFY(modifiedUrls.contains(url)); 0547 QVERIFY(modifiedUrls.contains(destUrl)); 0548 } 0549 0550 void DocumentTest::testMetaInfoJpeg() 0551 { 0552 QUrl url = urlForTestFile("orient6.jpg"); 0553 Document::Ptr doc = DocumentFactory::instance()->load(url); 0554 0555 // We cleared the cache, so the document should not be loaded 0556 Q_ASSERT(doc->loadingState() <= Document::KindDetermined); 0557 0558 // Wait until we receive the metaInfoUpdated() signal 0559 QSignalSpy metaInfoUpdatedSpy(doc.data(), SIGNAL(metaInfoUpdated())); 0560 while (metaInfoUpdatedSpy.isEmpty()) { 0561 QTest::qWait(100); 0562 } 0563 0564 // Extract an exif key 0565 QString value = doc->metaInfo()->getValueForKey("Exif.Image.Make"); 0566 QCOMPARE(value, QString::fromUtf8("Canon")); 0567 } 0568 0569 void DocumentTest::testMetaInfoBmp() 0570 { 0571 QUrl url = urlForTestOutputFile("metadata.bmp"); 0572 const int width = 200; 0573 const int height = 100; 0574 QImage image(width, height, QImage::Format_ARGB32); 0575 image.fill(Qt::black); 0576 image.save(url.toLocalFile(), "BMP"); 0577 0578 Document::Ptr doc = DocumentFactory::instance()->load(url); 0579 QSignalSpy metaInfoUpdatedSpy(doc.data(), SIGNAL(metaInfoUpdated())); 0580 waitUntilMetaInfoLoaded(doc); 0581 0582 Q_ASSERT(metaInfoUpdatedSpy.count() >= 1); 0583 0584 QString value = doc->metaInfo()->getValueForKey("General.ImageSize"); 0585 QString expectedValue = QStringLiteral("%1x%2").arg(width).arg(height); 0586 QCOMPARE(value, expectedValue); 0587 } 0588 0589 void DocumentTest::testForgetModifiedDocument() 0590 { 0591 QSignalSpy spy(DocumentFactory::instance(), SIGNAL(modifiedDocumentListChanged())); 0592 DocumentFactory::instance()->forget(QUrl("file://does/not/exist.png")); 0593 QCOMPARE(spy.count(), 0); 0594 0595 // Generate test image 0596 QImage image1(200, 96, QImage::Format_RGB32); 0597 { 0598 QPainter painter(&image1); 0599 QConicalGradient gradient(QPointF(100, 48), 100); 0600 gradient.setColorAt(0, Qt::white); 0601 gradient.setColorAt(1, Qt::blue); 0602 painter.fillRect(image1.rect(), gradient); 0603 } 0604 0605 QUrl url = urlForTestOutputFile("testForgetModifiedDocument.png"); 0606 QVERIFY(image1.save(url.toLocalFile(), "png")); 0607 0608 // Load it as a Gwenview document 0609 Document::Ptr doc = DocumentFactory::instance()->load(url); 0610 doc->waitUntilLoaded(); 0611 0612 // Modify it 0613 auto op = new TransformImageOperation(ROT_90); 0614 op->applyToDocument(doc); 0615 QTest::qWait(100); 0616 0617 QCOMPARE(spy.count(), 1); 0618 0619 QList<QUrl> lst = DocumentFactory::instance()->modifiedDocumentList(); 0620 QCOMPARE(lst.length(), 1); 0621 QCOMPARE(lst.first(), url); 0622 0623 // Forget it 0624 DocumentFactory::instance()->forget(url); 0625 0626 QCOMPARE(spy.count(), 2); 0627 lst = DocumentFactory::instance()->modifiedDocumentList(); 0628 QVERIFY(lst.isEmpty()); 0629 } 0630 0631 void DocumentTest::testModifiedAndSavedSignals() 0632 { 0633 TransformImageOperation *op; 0634 0635 QUrl url = urlForTestFile("orient6.jpg"); 0636 Document::Ptr doc = DocumentFactory::instance()->load(url); 0637 QSignalSpy modifiedSpy(doc.data(), SIGNAL(modified(QUrl))); 0638 QSignalSpy savedSpy(doc.data(), SIGNAL(saved(QUrl, QUrl))); 0639 doc->waitUntilLoaded(); 0640 0641 QCOMPARE(modifiedSpy.count(), 0); 0642 QCOMPARE(savedSpy.count(), 0); 0643 0644 op = new TransformImageOperation(ROT_90); 0645 op->applyToDocument(doc); 0646 QTest::qWait(100); 0647 QCOMPARE(modifiedSpy.count(), 1); 0648 0649 op = new TransformImageOperation(ROT_90); 0650 op->applyToDocument(doc); 0651 QTest::qWait(100); 0652 QCOMPARE(modifiedSpy.count(), 2); 0653 0654 doc->undoStack()->undo(); 0655 QTest::qWait(100); 0656 QCOMPARE(modifiedSpy.count(), 3); 0657 0658 doc->undoStack()->undo(); 0659 QTest::qWait(100); 0660 QCOMPARE(savedSpy.count(), 1); 0661 } 0662 0663 class TestJob : public DocumentJob 0664 { 0665 public: 0666 TestJob(QString *str, char ch) 0667 : mStr(str) 0668 , mCh(ch) 0669 { 0670 } 0671 0672 protected: 0673 void doStart() override 0674 { 0675 *mStr += mCh; 0676 emitResult(); 0677 } 0678 0679 private: 0680 QString *mStr; 0681 char mCh; 0682 }; 0683 0684 void DocumentTest::testJobQueue() 0685 { 0686 QUrl url = urlForTestFile("orient6.jpg"); 0687 Document::Ptr doc = DocumentFactory::instance()->load(url); 0688 QSignalSpy spy(doc.data(), SIGNAL(busyChanged(QUrl, bool))); 0689 0690 QString str; 0691 doc->enqueueJob(new TestJob(&str, 'a')); 0692 doc->enqueueJob(new TestJob(&str, 'b')); 0693 doc->enqueueJob(new TestJob(&str, 'c')); 0694 QVERIFY(doc->isBusy()); 0695 QEventLoop loop; 0696 connect(doc.data(), &Document::allTasksDone, &loop, &QEventLoop::quit); 0697 loop.exec(); 0698 QVERIFY(!doc->isBusy()); 0699 QCOMPARE(spy.count(), 2); 0700 QVariantList row = spy.takeFirst(); 0701 QCOMPARE(row.at(0).toUrl(), url); 0702 QVERIFY(row.at(1).toBool()); 0703 row = spy.takeFirst(); 0704 QCOMPARE(row.at(0).toUrl(), url); 0705 QVERIFY(!row.at(1).toBool()); 0706 QCOMPARE(str, QStringLiteral("abc")); 0707 } 0708 0709 class TestCheckDocumentEditorJob : public DocumentJob 0710 { 0711 public: 0712 TestCheckDocumentEditorJob(int *hasEditor) 0713 : mHasEditor(hasEditor) 0714 { 0715 *mHasEditor = -1; 0716 } 0717 0718 protected: 0719 void doStart() override 0720 { 0721 document()->waitUntilLoaded(); 0722 *mHasEditor = checkDocumentEditor() ? 1 : 0; 0723 emitResult(); 0724 } 0725 0726 private: 0727 int *mHasEditor; 0728 }; 0729 0730 class TestUiDelegate : public KJobUiDelegate 0731 { 0732 public: 0733 TestUiDelegate(bool *showErrorMessageCalled) 0734 : mShowErrorMessageCalled(showErrorMessageCalled) 0735 { 0736 setAutoErrorHandlingEnabled(true); 0737 *mShowErrorMessageCalled = false; 0738 } 0739 0740 void showErrorMessage() override 0741 { 0742 // qDebug(); 0743 *mShowErrorMessageCalled = true; 0744 } 0745 0746 private: 0747 bool *mShowErrorMessageCalled; 0748 }; 0749 0750 /** 0751 * Test that an error is reported when a DocumentJob fails because there is no 0752 * document editor available 0753 */ 0754 void DocumentTest::testCheckDocumentEditor() 0755 { 0756 int hasEditor; 0757 bool showErrorMessageCalled; 0758 QEventLoop loop; 0759 Document::Ptr doc; 0760 TestCheckDocumentEditorJob *job; 0761 0762 doc = DocumentFactory::instance()->load(urlForTestFile("orient6.jpg")); 0763 0764 job = new TestCheckDocumentEditorJob(&hasEditor); 0765 job->setUiDelegate(new TestUiDelegate(&showErrorMessageCalled)); 0766 doc->enqueueJob(job); 0767 connect(doc.data(), &Document::allTasksDone, &loop, &QEventLoop::quit); 0768 loop.exec(); 0769 QVERIFY(!showErrorMessageCalled); 0770 QCOMPARE(hasEditor, 1); 0771 0772 doc = DocumentFactory::instance()->load(urlForTestFile("test.svg")); 0773 0774 job = new TestCheckDocumentEditorJob(&hasEditor); 0775 job->setUiDelegate(new TestUiDelegate(&showErrorMessageCalled)); 0776 doc->enqueueJob(job); 0777 connect(doc.data(), &Document::allTasksDone, &loop, &QEventLoop::quit); 0778 loop.exec(); 0779 QVERIFY(showErrorMessageCalled); 0780 QCOMPARE(hasEditor, 0); 0781 } 0782 0783 /** 0784 * An operation should only pushed to the document undo stack if it succeed 0785 */ 0786 void DocumentTest::testUndoStackPush() 0787 { 0788 class SuccessOperation : public AbstractImageOperation 0789 { 0790 protected: 0791 void redo() override 0792 { 0793 QMetaObject::invokeMethod( 0794 this, 0795 [this]() { 0796 finish(true); 0797 }, 0798 Qt::QueuedConnection); 0799 } 0800 }; 0801 0802 class FailureOperation : public AbstractImageOperation 0803 { 0804 protected: 0805 void redo() override 0806 { 0807 QMetaObject::invokeMethod( 0808 this, 0809 [this]() { 0810 finish(false); 0811 }, 0812 Qt::QueuedConnection); 0813 } 0814 }; 0815 0816 AbstractImageOperation *op; 0817 Document::Ptr doc = DocumentFactory::instance()->load(urlForTestFile("orient6.jpg")); 0818 0819 // A successful operation should be added to the undo stack 0820 op = new SuccessOperation; 0821 op->applyToDocument(doc); 0822 QTest::qWait(100); 0823 QVERIFY(!doc->undoStack()->isClean()); 0824 0825 // Reset 0826 doc->undoStack()->undo(); 0827 QVERIFY(doc->undoStack()->isClean()); 0828 0829 // A failed operation should not be added to the undo stack 0830 op = new FailureOperation; 0831 op->applyToDocument(doc); 0832 QTest::qWait(100); 0833 QVERIFY(doc->undoStack()->isClean()); 0834 } 0835 0836 void DocumentTest::testUndoRedo() 0837 { 0838 class SuccessOperation : public AbstractImageOperation 0839 { 0840 public: 0841 int mRedoCount = 0; 0842 int mUndoCount = 0; 0843 0844 protected: 0845 void redo() override 0846 { 0847 mRedoCount++; 0848 finish(true); 0849 } 0850 0851 void undo() override 0852 { 0853 mUndoCount++; 0854 finish(true); 0855 } 0856 }; 0857 0858 Document::Ptr doc = DocumentFactory::instance()->load(urlForTestFile("orient6.jpg")); 0859 QSignalSpy modifiedSpy(doc.data(), &Document::modified); 0860 QSignalSpy savedSpy(doc.data(), &Document::saved); 0861 0862 auto op = new SuccessOperation; 0863 QCOMPARE(op->mRedoCount, 0); 0864 QCOMPARE(op->mUndoCount, 0); 0865 0866 // Apply (redo) operation 0867 op->applyToDocument(doc); 0868 QVERIFY(modifiedSpy.wait()); 0869 QCOMPARE(op->mRedoCount, 1); 0870 QCOMPARE(op->mUndoCount, 0); 0871 QCOMPARE(doc->undoStack()->count(), 1); 0872 QVERIFY(!doc->undoStack()->isClean()); 0873 0874 // Undo operation 0875 doc->undoStack()->undo(); 0876 QVERIFY(savedSpy.wait()); 0877 QCOMPARE(op->mRedoCount, 1); 0878 QCOMPARE(op->mUndoCount, 1); 0879 QCOMPARE(doc->undoStack()->count(), 1); 0880 QVERIFY(doc->undoStack()->isClean()); 0881 0882 // Redo operation 0883 doc->undoStack()->redo(); 0884 QVERIFY(modifiedSpy.wait()); 0885 QCOMPARE(op->mRedoCount, 2); 0886 QCOMPARE(op->mUndoCount, 1); 0887 QCOMPARE(doc->undoStack()->count(), 1); 0888 QVERIFY(!doc->undoStack()->isClean()); 0889 0890 // Undo operation again 0891 doc->undoStack()->undo(); 0892 QVERIFY(savedSpy.wait()); 0893 QCOMPARE(op->mRedoCount, 2); 0894 QCOMPARE(op->mUndoCount, 2); 0895 QCOMPARE(doc->undoStack()->count(), 1); 0896 QVERIFY(doc->undoStack()->isClean()); 0897 } 0898 0899 #include "moc_documenttest.cpp"