1 /*
2 This file is part of the KDE libraries
3
4 SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 #include "kfuzzymatchertest.h"
9
10 #include <QString>
11 #include <QStringList>
12 #include <QTest>
13
14 #include <algorithm>
15
16 #include "kfuzzymatcher.h"
17
QTEST_MAIN(KFuzzyMatcherTest)18 QTEST_MAIN(KFuzzyMatcherTest)
19
20 void KFuzzyMatcherTest::testMatchSimple_data()
21 {
22 QTest::addColumn<QString>("pattern");
23 QTest::addColumn<QString>("inputstr");
24 QTest::addColumn<bool>("expected");
25
26 QTest::newRow("AbcD") << QStringLiteral("AbcD") << QStringLiteral("AbCdefg") << true;
27 QTest::newRow("WithSpace") << QStringLiteral("Wa qa") << QStringLiteral("Wa qar") << true;
28 QTest::newRow("RTL") << QStringLiteral("ارو") << QStringLiteral("اردو") << true;
29 QTest::newRow("WithSep") << QStringLiteral("tf") << QStringLiteral("the_file") << true;
30 QTest::newRow("Umlaut") << QStringLiteral("Häu") << QStringLiteral("Häuser") << true;
31 QTest::newRow("Unmatched") << QStringLiteral("Name") << QStringLiteral("Nam") << false;
32 QTest::newRow("Empty Pattern") << QStringLiteral("") << QStringLiteral("Nam") << true;
33 }
34
testMatchSimple()35 void KFuzzyMatcherTest::testMatchSimple()
36 {
37 QFETCH(QString, pattern);
38 QFETCH(QString, inputstr);
39 QFETCH(bool, expected);
40
41 QVERIFY(KFuzzyMatcher::matchSimple(pattern, inputstr) == expected);
42 }
43
testMatch_data()44 void KFuzzyMatcherTest::testMatch_data()
45 {
46 QTest::addColumn<QString>("pattern");
47 QTest::addColumn<QStringList>("input");
48 QTest::addColumn<QStringList>("expected");
49 QTest::addColumn<int>("size");
50 // clang-format off
51 QTest::newRow("pattern=sort") << QStringLiteral("sort")
52 << QStringList{
53 QStringLiteral("Sort"),
54 QStringLiteral("Some other right test"),
55 QStringLiteral("Soup rate"),
56 QStringLiteral("Someother"),
57 QStringLiteral("irrelevant"),
58 }
59 << QStringList{
60 QStringLiteral("Sort"),
61 QStringLiteral("Some other right test"),
62 QStringLiteral("Soup rate"),
63 }
64 << 3;
65
66
67 QTest::newRow("pattern=kateapp") << QStringLiteral("kaapp")
68 << QStringList{
69 QStringLiteral("kateapp.cpp"),
70 QStringLiteral("kate_application"),
71 QStringLiteral("kateapp.h"),
72 QStringLiteral("katepap.c")
73 }
74 << QStringList{
75 QStringLiteral("kate_application"),
76 QStringLiteral("kateapp.h"),
77 QStringLiteral("kateapp.cpp")
78 }
79 << 3;
80
81 QTest::newRow("pattern=this") << QStringLiteral("this")
82 << QStringList{
83 QStringLiteral("th"),
84 QStringLiteral("ths"),
85 QStringLiteral("thsi")
86 }
87 << QStringList{
88 }
89 << 0;
90
91 QTest::newRow("pattern=marath") << QStringLiteral("marath")
92 << QStringList{
93 QStringLiteral("Maralen of the Mornsong"),
94 QStringLiteral("Silumgar, the Drifting Death"),
95 QStringLiteral("Maralen of the Mornsong Avatar"),
96 QStringLiteral("Marshaling the Troops"),
97 QStringLiteral("Homeward Path"),
98 QStringLiteral("Marath, Will of the Wild"),
99 QStringLiteral("Marshal's Anthem"),
100 QStringLiteral("Marchesa, the Black Rose"),
101 QStringLiteral("Mark for Death"),
102 QStringLiteral("Master Apothecary"),
103 QStringLiteral("Mazirek, Kraul Death Priest"),
104 QStringLiteral("Akroma, Angel of Wrath"),
105 QStringLiteral("Akroma, Angel of Wrath Avatar"),
106 QStringLiteral("Commander's Authority"),
107 QStringLiteral("Shaman of the Great Hunt"),
108 QStringLiteral("Halimar Wavewatch"),
109 QStringLiteral("Pyromancer's Swath")
110 }
111 << QStringList{
112 QStringLiteral("Marath, Will of the Wild"),
113 QStringLiteral("Maralen of the Mornsong"),
114 QStringLiteral("Maralen of the Mornsong Avatar"),
115 QStringLiteral("Marshal's Anthem"),
116 QStringLiteral("Marshaling the Troops"),
117 QStringLiteral("Marchesa, the Black Rose"),
118 QStringLiteral("Mark for Death"),
119 QStringLiteral("Master Apothecary"),
120 QStringLiteral("Mazirek, Kraul Death Priest"),
121 QStringLiteral("Akroma, Angel of Wrath"),
122 QStringLiteral("Akroma, Angel of Wrath Avatar"),
123 QStringLiteral("Commander's Authority"),
124 QStringLiteral("Homeward Path"),
125 QStringLiteral("Shaman of the Great Hunt"),
126 QStringLiteral("Halimar Wavewatch"),
127 QStringLiteral("Pyromancer's Swath"),
128 QStringLiteral("Silumgar, the Drifting Death")
129 }
130 << 17;
131
132 // This tests our recursive best match
133 QTest::newRow("pattern=lll") << QStringLiteral("lll")
134 << QStringList{
135 QStringLiteral("SVisualLoggerLogsList.h"),
136 QStringLiteral("SimpleFileLogger.cpp"),
137 QStringLiteral("StringHandlerLogList.txt"),
138 QStringLiteral("LeapFromLostAllan"),
139 QStringLiteral("BumpLLL"),
140 }
141 << QStringList{
142 QStringLiteral("SVisualLoggerLogsList.h"),
143 QStringLiteral("LeapFromLostAllan"),
144 QStringLiteral("BumpLLL"),
145 QStringLiteral("StringHandlerLogList.txt"),
146 QStringLiteral("SimpleFileLogger.cpp"),
147 }
148 << 5;
149
150 QTest::newRow("pattern=") << QStringLiteral("")
151 << QStringList{
152 QStringLiteral("th"),
153 QStringLiteral("ths"),
154 QStringLiteral("thsi")
155 }
156 << QStringList{
157 QStringLiteral("th"),
158 QStringLiteral("ths"),
159 QStringLiteral("thsi")
160 }
161 << 3;
162 // clang-format on
163 }
164
matchHelper(const QString & pattern,const QStringList & input)165 static QStringList matchHelper(const QString &pattern, const QStringList &input)
166 {
167 QVector<QPair<QString, int>> actual;
168 for (int i = 0; i < input.size(); ++i) {
169 KFuzzyMatcher::Result res = KFuzzyMatcher::match(pattern, input.at(i));
170 if (res.matched) {
171 actual.push_back({input.at(i), res.score});
172 }
173 }
174
175 // sort descending based on score
176 std::sort(actual.begin(), actual.end(), [](const QPair<QString, int> &l, const QPair<QString, int> &r) {
177 return l.second > r.second;
178 });
179
180 QStringList actualOut;
181 for (const auto &s : actual) {
182 actualOut << s.first;
183 }
184 return actualOut;
185 }
186
testMatch()187 void KFuzzyMatcherTest::testMatch()
188 {
189 QFETCH(QString, pattern);
190 QFETCH(QStringList, input);
191 QFETCH(QStringList, expected);
192 QFETCH(int, size);
193
194 const QStringList actual = matchHelper(pattern, input);
195
196 QCOMPARE(actual.size(), size);
197 QCOMPARE(actual, expected);
198 }
199
testMatchedRanges_data()200 void KFuzzyMatcherTest::testMatchedRanges_data()
201 {
202 QTest::addColumn<QString>("pattern");
203 QTest::addColumn<QString>("string");
204
205 using Range = QPair<int, int>;
206 QTest::addColumn<QVector<Range>>("expectedRanges");
207
208 QTest::addColumn<bool>("matchingOnly");
209
210 QTest::newRow("Emtpy") << QStringLiteral("") << QStringLiteral("") << QVector<Range>{} << true;
211 QTest::newRow("Hello") << QStringLiteral("Hlo") << QStringLiteral("Hello") << QVector<Range>{{0, 1}, {3, 2}} << true;
212 QTest::newRow("lll") << QStringLiteral("lll") << QStringLiteral("SVisualLoggerLogsList") << QVector<Range>{{7, 1}, {13, 1}, {17, 1}} << true;
213 QTest::newRow("Sort") << QStringLiteral("sort") << QStringLiteral("SorT") << QVector<Range>{{0, 4}} << true;
214 QTest::newRow("Unmatching") << QStringLiteral("git") << QStringLiteral("gti") << QVector<Range>{} << true;
215 QTest::newRow("UnmatchingWithAllMatches") << QStringLiteral("git") << QStringLiteral("gti") << QVector<Range>{{0, 1}, {2, 1}} << false;
216 }
217
testMatchedRanges()218 void KFuzzyMatcherTest::testMatchedRanges()
219 {
220 QFETCH(QString, pattern);
221 QFETCH(QString, string);
222 QFETCH(bool, matchingOnly);
223 using Range = QPair<int, int>;
224 QFETCH(QVector<Range>, expectedRanges);
225
226 const auto matchMode = matchingOnly ? KFuzzyMatcher::RangeType::FullyMatched : KFuzzyMatcher::RangeType::All;
227
228 auto resultRanges = KFuzzyMatcher::matchedRanges(pattern, string, matchMode);
229 QCOMPARE(resultRanges.size(), expectedRanges.size());
230
231 bool res = std::equal(expectedRanges.begin(), expectedRanges.end(), resultRanges.begin(), [](const Range &l, const KFuzzyMatcher::Range &r) {
232 return l.first == r.start && l.second == r.length;
233 });
234 QVERIFY(res);
235 }
236