File indexing completed on 2024-12-08 09:44:26

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "kreplacetest.h"
0009 
0010 #include <assert.h>
0011 #include <stdlib.h>
0012 
0013 #include <QApplication>
0014 #include <QDebug>
0015 #include <QEventLoop>
0016 #include <QPushButton>
0017 
0018 #include <kreplace.h>
0019 #include <kreplacedialog.h>
0020 
0021 void KReplaceTest::enterLoop()
0022 {
0023     QEventLoop eventLoop;
0024     connect(this, &KReplaceTest::exitLoop, &eventLoop, &QEventLoop::quit);
0025     eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
0026 }
0027 
0028 KReplaceTest::KReplaceTest(const QStringList &text, const QString &buttonName)
0029     : QObject(nullptr)
0030     , m_text(text)
0031     , m_replace(nullptr)
0032     , m_buttonName(buttonName)
0033 {
0034 }
0035 
0036 KReplaceTest::~KReplaceTest()
0037 {
0038 }
0039 
0040 void KReplaceTest::replace(const QString &pattern, const QString &replacement, long options)
0041 {
0042     m_needEventLoop = false;
0043     // This creates a replace-next-prompt dialog if needed.
0044     m_replace.reset(new KReplace(pattern, replacement, options));
0045 
0046     // Connect highlight (or textFound) signal to code which handles highlighting of found text.
0047 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 81)
0048     connect(m_replace.get(), qOverload<const QString &, int, int>(&KFind::highlight), this, &KReplaceTest::slotHighlight);
0049 #else
0050     connect(m_replace.get(), &KFind::textFound, this, &KReplaceTest::slotHighlight);
0051 #endif
0052 
0053     // Connect findNext signal - called when pressing the button in the dialog
0054     connect(m_replace.get(), &KFind::findNext, this, &KReplaceTest::slotReplaceNext);
0055 
0056     // Connect replace signal - called when doing a replacement
0057 #if KTEXTWIDGETS_ENABLE_DEPRECATED_SINCE(5, 83)
0058     connect(m_replace.get(), qOverload<const QString &, int, int, int>(&KReplace::replace), this, &KReplaceTest::slotReplace);
0059 #else
0060     connect(m_replace.get(), &KReplace::textReplaced, this, &KReplaceTest::slotReplace);
0061 #endif
0062 
0063     // Go to initial position
0064     if ((options & KFind::FromCursor) == 0) {
0065         if (m_text.isEmpty()) {
0066             return;
0067         }
0068         if (m_replace->options() & KFind::FindBackwards) {
0069             m_currentPos = --m_text.end();
0070         } else {
0071             m_currentPos = m_text.begin();
0072         }
0073     }
0074 
0075     // Launch first replacement
0076     slotReplaceNext();
0077 
0078     if (m_needEventLoop) {
0079         enterLoop();
0080     }
0081 }
0082 
0083 void KReplaceTest::slotHighlight(const QString &str, int matchingIndex, int matchedLength)
0084 {
0085     qDebug() << "slotHighlight Index:" << matchingIndex << " Length:" << matchedLength << " Substr:" << str.mid(matchingIndex, matchedLength);
0086     // Emulate the user saying yes
0087     // We need Qt::QueuedConnection (and the enterloop/exitloop)
0088     // otherwise we get an infinite loop (Match never returned,
0089     // so slotReplaceNext never returns)
0090     if (m_replace->options() & KReplaceDialog::PromptOnReplace) {
0091         QDialog *dlg = m_replace->replaceNextDialog(false);
0092         disconnect(dlg, &QDialog::finished, m_replace.get(), nullptr); // hack to avoid slotDialogClosed being called
0093         dlg->hide();
0094 
0095         QPushButton *button = dlg->findChild<QPushButton *>(m_buttonName);
0096         auto clickFunc = [button]() {
0097             button->click();
0098         };
0099         QMetaObject::invokeMethod(button, clickFunc, Qt::QueuedConnection);
0100 
0101         m_needEventLoop = true;
0102     }
0103 }
0104 
0105 void KReplaceTest::slotReplace(const QString &text, int replacementIndex, int replacedLength, int matchedLength)
0106 {
0107     Q_UNUSED(replacementIndex);
0108     Q_UNUSED(replacedLength);
0109     Q_UNUSED(matchedLength);
0110     // qDebug() << "index=" << replacementIndex << " replacedLength=" << replacedLength << " matchedLength=" << matchedLength << " text=" << text.left( 50 );
0111     *m_currentPos = text; // KReplace hacked the replacement into 'text' in already.
0112 }
0113 
0114 void KReplaceTest::slotReplaceNext()
0115 {
0116     // qDebug();
0117     KFind::Result res = KFind::NoMatch;
0118     int backwards = m_replace->options() & KFind::FindBackwards;
0119     while (res == KFind::NoMatch) {
0120         if (m_replace->needData()) {
0121             m_replace->setData(*m_currentPos);
0122         }
0123 
0124         // Let KReplace inspect the text fragment, and display a dialog if a match is found
0125         res = m_replace->replace();
0126 
0127         if (res == KFind::NoMatch) {
0128             QStringList::iterator lastItem = backwards ? m_text.begin() : --m_text.end();
0129             if (m_currentPos == lastItem) {
0130                 break;
0131             }
0132             if (m_replace->options() & KFind::FindBackwards) {
0133                 m_currentPos--;
0134             } else {
0135                 m_currentPos++;
0136             }
0137         }
0138     }
0139 
0140 #if 0 // commented out so that this test doesn't require interaction
0141     if (res == KFind::NoMatch)   // i.e. at end
0142         if (m_replace->shouldRestart()) {
0143             if (m_replace->options() & KFind::FindBackwards) {
0144                 m_currentPos = m_text.fromLast();
0145             } else {
0146                 m_currentPos = m_text.begin();
0147             }
0148             slotReplaceNext();
0149         }
0150 #endif
0151     if (res == KFind::NoMatch && m_needEventLoop) {
0152         Q_EMIT exitLoop();
0153     }
0154 }
0155 
0156 void KReplaceTest::print()
0157 {
0158     QStringList::Iterator it = m_text.begin();
0159     for (; it != m_text.end(); ++it) {
0160         qDebug() << *it;
0161     }
0162 }
0163 
0164 /* button is the button that we emulate pressing, when options includes PromptOnReplace.
0165    Valid possibilities are User1 (replace all) and User3 (replace) */
0166 static void testReplaceSimple(int options, const QString &buttonName = QString())
0167 {
0168     qDebug() << "testReplaceSimple: " << options;
0169     KReplaceTest test(QStringList() << QStringLiteral("hellohello"), buttonName);
0170     test.replace(QStringLiteral("hello"), QStringLiteral("HELLO"), options);
0171     QStringList textLines = test.textLines();
0172     assert(textLines.count() == 1);
0173     if (textLines[0] != QLatin1String("HELLOHELLO")) {
0174         qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of 'HELLOHELLO'";
0175         exit(1);
0176     }
0177 }
0178 
0179 // Replacing "a" with "".
0180 // input="aaaaaa", expected output=""
0181 static void testReplaceBlank(int options, const QString &buttonName = QString())
0182 {
0183     qDebug() << "testReplaceBlank: " << options;
0184     KReplaceTest test(QStringList() << QStringLiteral("aaaaaa"), buttonName);
0185     test.replace(QStringLiteral("a"), QString(), options);
0186     QStringList textLines = test.textLines();
0187     assert(textLines.count() == 1);
0188     if (!textLines[0].isEmpty()) {
0189         qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of ''";
0190         exit(1);
0191     }
0192 }
0193 
0194 // Replacing "" with "foo"
0195 // input="bbbb", expected output="foobfoobfoobfoobfoo"
0196 static void testReplaceBlankSearch(int options, const QString &buttonName = QString())
0197 {
0198     qDebug() << "testReplaceBlankSearch: " << options;
0199     KReplaceTest test(QStringList() << QStringLiteral("bbbb"), buttonName);
0200     test.replace(QString(), QStringLiteral("foo"), options);
0201     QStringList textLines = test.textLines();
0202     assert(textLines.count() == 1);
0203     if (textLines[0] != QLatin1String("foobfoobfoobfoobfoo")) {
0204         qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of 'foobfoobfoobfoobfoo'";
0205         exit(1);
0206     }
0207 }
0208 
0209 static void testReplaceLonger(int options, const QString &buttonName = QString())
0210 {
0211     qDebug() << "testReplaceLonger: " << options;
0212     // Standard test of a replacement string longer than the matched string
0213     KReplaceTest test(QStringList() << QStringLiteral("aaaa"), buttonName);
0214     test.replace(QStringLiteral("a"), QStringLiteral("bb"), options);
0215     QStringList textLines = test.textLines();
0216     assert(textLines.count() == 1);
0217     if (textLines[0] != QLatin1String("bbbbbbbb")) {
0218         qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of 'bbbbbbbb'";
0219         exit(1);
0220     }
0221 }
0222 
0223 static void testReplaceLongerInclude(int options, const QString &buttonName = QString())
0224 {
0225     qDebug() << "testReplaceLongerInclude: " << options;
0226     // Similar test, where the replacement string includes the search string
0227     KReplaceTest test(QStringList() << QStringLiteral("a foo b"), buttonName);
0228     test.replace(QStringLiteral("foo"), QStringLiteral("foobar"), options);
0229     QStringList textLines = test.textLines();
0230     assert(textLines.count() == 1);
0231     if (textLines[0] != QLatin1String("a foobar b")) {
0232         qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of 'a foobar b'";
0233         exit(1);
0234     }
0235 }
0236 
0237 static void testReplaceLongerInclude2(int options, const QString &buttonName = QString())
0238 {
0239     qDebug() << "testReplaceLongerInclude2: " << options;
0240     // Similar test, but with more chances of matches inside the replacement string
0241     KReplaceTest test(QStringList() << QStringLiteral("aaaa"), buttonName);
0242     test.replace(QStringLiteral("a"), QStringLiteral("aa"), options);
0243     QStringList textLines = test.textLines();
0244     assert(textLines.count() == 1);
0245     if (textLines[0] != QLatin1String("aaaaaaaa")) {
0246         qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of 'aaaaaaaa'";
0247         exit(1);
0248     }
0249 }
0250 
0251 // Test for the \0 backref
0252 static void testReplaceBackRef(int options, const QString &buttonName = QString())
0253 {
0254     KReplaceTest test(QStringList() << QStringLiteral("abc def"), buttonName);
0255     test.replace(QStringLiteral("abc"), QStringLiteral("(\\0)"), options);
0256     QStringList textLines = test.textLines();
0257     assert(textLines.count() == 1);
0258     QString expected = options & KReplaceDialog::BackReference ? QStringLiteral("(abc) def") : QStringLiteral("(\\0) def");
0259     if (textLines[0] != expected) {
0260         qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of '" << expected << "'";
0261         exit(1);
0262     }
0263 }
0264 
0265 // Test for other backrefs
0266 static void testReplaceBackRef1(int options, const QString &buttonName = QString())
0267 {
0268     KReplaceTest test(QStringList() << QStringLiteral("a1 b2 a3"), buttonName);
0269     test.replace(QStringLiteral("([ab])([\\d])"), QStringLiteral("\\1 and \\2 in (\\0)"), options);
0270     QStringList textLines = test.textLines();
0271     assert(textLines.count() == 1);
0272     QString expected = QStringLiteral("a and 1 in (a1) b and 2 in (b2) a and 3 in (a3)");
0273     if (textLines[0] != expected) {
0274         qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of '" << expected << "'";
0275         exit(1);
0276     }
0277 }
0278 
0279 static void testReplacementHistory(const QStringList &findHistory, const QStringList &replaceHistory)
0280 {
0281     KReplaceDialog dlg(nullptr, 0, findHistory, replaceHistory);
0282     dlg.show();
0283     qDebug() << "testReplacementHistory:" << dlg.replacementHistory();
0284     assert(dlg.replacementHistory() == replaceHistory);
0285 }
0286 
0287 static void testReplacementHistory()
0288 {
0289     QStringList findHistory;
0290     QStringList replaceHistory;
0291     findHistory << QStringLiteral("foo") << QStringLiteral("bar");
0292     replaceHistory << QStringLiteral("FOO") << QStringLiteral("BAR");
0293     testReplacementHistory(findHistory, replaceHistory);
0294 
0295     findHistory.clear();
0296     replaceHistory.clear();
0297     findHistory << QStringLiteral("foo") << QStringLiteral("bar");
0298     replaceHistory << QString() << QStringLiteral("baz"); // #130831
0299     testReplacementHistory(findHistory, replaceHistory);
0300 }
0301 
0302 int main(int argc, char **argv)
0303 {
0304     QApplication::setApplicationName(QStringLiteral("kreplacetest"));
0305     QApplication app(argc, argv);
0306 
0307     testReplacementHistory(); // #130831
0308 
0309     testReplaceBlank(0);
0310     testReplaceBlank(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0311     testReplaceBlank(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0312     testReplaceBlank(KFind::FindBackwards);
0313     testReplaceBlank(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0314     testReplaceBlank(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0315 
0316     testReplaceBlankSearch(0);
0317     testReplaceBlankSearch(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0318     testReplaceBlankSearch(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0319     testReplaceBlankSearch(KFind::FindBackwards);
0320     testReplaceBlankSearch(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0321     testReplaceBlankSearch(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0322 
0323     testReplaceSimple(0);
0324     testReplaceSimple(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0325     testReplaceSimple(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0326     testReplaceSimple(KFind::FindBackwards);
0327     testReplaceSimple(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0328     testReplaceSimple(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0329 
0330     testReplaceLonger(0);
0331     testReplaceLonger(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0332     testReplaceLonger(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0333     testReplaceLonger(KFind::FindBackwards);
0334     testReplaceLonger(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0335     testReplaceLonger(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0336 
0337     testReplaceLongerInclude(0);
0338     testReplaceLongerInclude(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0339     testReplaceLongerInclude(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0340     testReplaceLongerInclude(KFind::FindBackwards);
0341     testReplaceLongerInclude(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0342     testReplaceLongerInclude(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0343 
0344     testReplaceLongerInclude2(0);
0345     testReplaceLongerInclude2(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0346     testReplaceLongerInclude2(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0347     testReplaceLongerInclude2(KFind::FindBackwards);
0348     testReplaceLongerInclude2(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0349     testReplaceLongerInclude2(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0350 
0351     testReplaceBackRef(0);
0352     testReplaceBackRef(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0353     testReplaceBackRef(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0354 
0355     testReplaceBackRef(KFind::FindBackwards);
0356     testReplaceBackRef(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0357     testReplaceBackRef(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0358     testReplaceBackRef(KReplaceDialog::BackReference | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0359     testReplaceBackRef(KReplaceDialog::BackReference | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0360     testReplaceBackRef(KReplaceDialog::BackReference | KFind::FindBackwards);
0361     testReplaceBackRef(KReplaceDialog::BackReference | KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace
0362     testReplaceBackRef(KReplaceDialog::BackReference | KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all
0363 
0364     testReplaceBackRef1(KReplaceDialog::BackReference | KFind::RegularExpression, QStringLiteral("replaceButton")); // replace
0365     testReplaceBackRef1(KReplaceDialog::BackReference | KFind::RegularExpression, QStringLiteral("allButton")); // replace all
0366 
0367     QString text = QLatin1String("This file is part of the KDE project.\n") + QLatin1String("This library is free software; you can redistribute it and/or\n")
0368         + QLatin1String("modify it under the terms of the GNU Library General Public\n")
0369         + QLatin1String("License version 2, as published by the Free Software Foundation.\n") + QLatin1Char('\n')
0370         + QLatin1String("    This library is distributed in the hope that it will be useful,\n")
0371         + QLatin1String("    but WITHOUT ANY WARRANTY; without even the implied warranty of\n")
0372         + QLatin1String("    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n")
0373         + QLatin1String("    Library General Public License for more details.\n") + QLatin1Char('\n')
0374         + QLatin1String("    You should have received a copy of the GNU Library General Public License\n")
0375         + QLatin1String("    along with this library; see the file COPYING.LIB.  If not, write to\n")
0376         + QLatin1String("    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n") + QLatin1String("    Boston, MA 02110-1301, USA.\n")
0377         + QLatin1String("More tests:\n") + QLatin1String("ThisThis This, This. This\n") + QLatin1String("aGNU\n") + QLatin1String("free");
0378     KReplaceTest test(text.split(QLatin1Char('\n')), QStringLiteral("0"));
0379 
0380     test.replace(QStringLiteral("GNU"), QStringLiteral("KDE"), 0);
0381     test.replace(QStringLiteral("free"), QStringLiteral("*free*"), 0);
0382     test.replace(QStringLiteral("This"), QStringLiteral("THIS*"), KFind::FindBackwards);
0383 
0384     test.print();
0385     // return app.exec();
0386     return 0;
0387 }
0388 
0389 #include "moc_kreplacetest.cpp"