1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30
31#include <qtextboundaryfinder.h>
32#include <qtextcodec.h>
33#include <qfile.h>
34#include <qdebug.h>
35#include <qlist.h>
36
37#include <algorithm>
38
39class tst_QTextBoundaryFinder : public QObject
40{
41 Q_OBJECT
42private slots:
43#ifdef QT_BUILD_INTERNAL
44 void graphemeBoundariesDefault_data();
45 void graphemeBoundariesDefault();
46 void wordBoundariesDefault_data();
47 void wordBoundariesDefault();
48 void sentenceBoundariesDefault_data();
49 void sentenceBoundariesDefault();
50 void lineBoundariesDefault_data();
51 void lineBoundariesDefault();
52#endif
53
54 void graphemeBoundaries_manual_data();
55 void graphemeBoundaries_manual();
56
57 void wordBoundaries_manual_data();
58 void wordBoundaries_manual();
59 void sentenceBoundaries_manual_data();
60 void sentenceBoundaries_manual();
61 void lineBoundaries_manual_data();
62 void lineBoundaries_manual();
63
64 void emptyText_data();
65 void emptyText();
66 void fastConstructor();
67 void assignmentOperator();
68 void isAtSoftHyphen_data();
69 void isAtSoftHyphen();
70 void thaiLineBreak();
71};
72
73
74QT_BEGIN_NAMESPACE
75namespace QTest {
76
77template<>
78inline char *toString(const QTextBoundaryFinder::BoundaryReasons &flags)
79{
80 return qstrdup(QByteArray::number(int(flags)).constData());
81}
82
83template<>
84inline char *toString(const QList<int> &list)
85{
86 QByteArray s;
87 for (QList<int>::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) {
88 if (!s.isEmpty())
89 s += ", ";
90 s += QByteArray::number(*it);
91 }
92 s = "{ " + s + " }";
93 return qstrdup(s.constData());
94}
95
96} // namespace QTest
97QT_END_NAMESPACE
98
99#ifdef QT_BUILD_INTERNAL
100static void generateDataFromFile(const QString &fname)
101{
102 QTest::addColumn<QString>(name: "testString");
103 QTest::addColumn<QList<int> >(name: "expectedBreakPositions");
104
105 QString testFile = QFINDTESTDATA(fname);
106 QVERIFY2(!testFile.isEmpty(), (fname.toLatin1() + QByteArray(" not found!")));
107 QFile f(testFile);
108 QVERIFY(f.exists());
109
110 f.open(flags: QIODevice::ReadOnly);
111
112 int linenum = 0;
113 while (!f.atEnd()) {
114 linenum++;
115
116 QByteArray line = f.readLine();
117 if (line.startsWith(c: '#'))
118 continue;
119
120 QString test = QString::fromUtf8(str: line);
121 QString comments;
122 int hash = test.indexOf(c: '#');
123 if (hash > 0) {
124 comments = test.mid(position: hash + 1).simplified();
125 test = test.left(n: hash);
126 }
127
128 QString testString;
129 QList<int> expectedBreakPositions;
130 foreach (const QString &part, test.simplified().split(QLatin1Char(' '), Qt::SkipEmptyParts)) {
131 if (part.size() == 1) {
132 if (part.at(i: 0).unicode() == 0xf7)
133 expectedBreakPositions.append(t: testString.size());
134 else
135 QVERIFY(part.at(0).unicode() == 0xd7);
136 continue;
137 }
138 bool ok = true;
139 uint ucs4 = part.toInt(ok: &ok, base: 16);
140 QVERIFY(ok && ucs4 > 0);
141 if (QChar::requiresSurrogates(ucs4)) {
142 testString.append(c: QChar::highSurrogate(ucs4));
143 testString.append(c: QChar::lowSurrogate(ucs4));
144 } else {
145 testString.append(c: QChar(ucs4));
146 }
147 }
148 QVERIFY(!testString.isEmpty());
149 QVERIFY(!expectedBreakPositions.isEmpty());
150
151 if (!comments.isEmpty()) {
152 const QStringList lst = comments.simplified().split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts);
153 comments.clear();
154 foreach (const QString &part, lst) {
155 if (part.size() == 1) {
156 if (part.at(i: 0).unicode() == 0xf7)
157 comments += QLatin1Char('+');
158 else if (part.at(i: 0).unicode() == 0xd7)
159 comments += QLatin1Char('x');
160 continue;
161 }
162 if (part.startsWith(c: QLatin1Char('(')) && part.endsWith(c: QLatin1Char(')')))
163 comments += part;
164 }
165 }
166
167 const QByteArray nm = "line #" + QByteArray::number(linenum) + ": " + comments.toLatin1();
168 QTest::newRow(dataTag: nm.constData()) << testString << expectedBreakPositions;
169 }
170}
171#endif
172
173static void doTestData(const QString &testString, const QList<int> &expectedBreakPositions,
174 QTextBoundaryFinder::BoundaryType type,
175 QTextBoundaryFinder::BoundaryReasons reasons = QTextBoundaryFinder::BreakOpportunity)
176{
177 QVERIFY(!testString.isEmpty());
178
179 QTextBoundaryFinder boundaryFinder(type, testString);
180
181 // test toNextBoundary()
182 {
183 QList<int> actualBreakPositions;
184 do {
185 QVERIFY(boundaryFinder.isAtBoundary());
186 if (boundaryFinder.boundaryReasons() & reasons)
187 actualBreakPositions.append(t: boundaryFinder.position());
188 } while (boundaryFinder.toNextBoundary() != -1);
189 QCOMPARE(actualBreakPositions, expectedBreakPositions);
190 }
191 QCOMPARE(boundaryFinder.position(), -1);
192 QVERIFY(!boundaryFinder.isAtBoundary());
193 QVERIFY(boundaryFinder.boundaryReasons() == QTextBoundaryFinder::NotAtBoundary);
194
195 // test toPreviousBoundary()
196 {
197 QList<int> expectedBreakPositionsRev = expectedBreakPositions;
198 std::sort(first: expectedBreakPositionsRev.begin(), last: expectedBreakPositionsRev.end(), comp: std::greater<int>());
199
200 QList<int> actualBreakPositions;
201 boundaryFinder.toEnd();
202 do {
203 QVERIFY(boundaryFinder.isAtBoundary());
204 if (boundaryFinder.boundaryReasons() & reasons)
205 actualBreakPositions.append(t: boundaryFinder.position());
206 } while (boundaryFinder.toPreviousBoundary() != -1);
207 QCOMPARE(actualBreakPositions, expectedBreakPositionsRev);
208 }
209 QCOMPARE(boundaryFinder.position(), -1);
210 QVERIFY(!boundaryFinder.isAtBoundary());
211 QVERIFY(boundaryFinder.boundaryReasons() == QTextBoundaryFinder::NotAtBoundary);
212
213 // test boundaryReasons()
214 for (int i = 0; i <= testString.length(); ++i) {
215 boundaryFinder.setPosition(i);
216 QCOMPARE(!!(boundaryFinder.boundaryReasons() & reasons), expectedBreakPositions.contains(i));
217 }
218}
219
220#ifdef QT_BUILD_INTERNAL
221
222QT_BEGIN_NAMESPACE
223extern Q_AUTOTEST_EXPORT int qt_initcharattributes_default_algorithm_only;
224QT_END_NAMESPACE
225
226void tst_QTextBoundaryFinder::graphemeBoundariesDefault_data()
227{
228 generateDataFromFile(fname: "data/GraphemeBreakTest.txt");
229}
230
231void tst_QTextBoundaryFinder::graphemeBoundariesDefault()
232{
233 QFETCH(QString, testString);
234 QFETCH(QList<int>, expectedBreakPositions);
235
236 QScopedValueRollback<int> default_algorithm(qt_initcharattributes_default_algorithm_only);
237 qt_initcharattributes_default_algorithm_only++;
238
239 doTestData(testString, expectedBreakPositions, type: QTextBoundaryFinder::Grapheme);
240}
241
242void tst_QTextBoundaryFinder::wordBoundariesDefault_data()
243{
244 generateDataFromFile(fname: "data/WordBreakTest.txt");
245}
246
247void tst_QTextBoundaryFinder::wordBoundariesDefault()
248{
249 QFETCH(QString, testString);
250 QFETCH(QList<int>, expectedBreakPositions);
251
252 QScopedValueRollback<int> default_algorithm(qt_initcharattributes_default_algorithm_only);
253 qt_initcharattributes_default_algorithm_only++;
254
255 doTestData(testString, expectedBreakPositions, type: QTextBoundaryFinder::Word);
256}
257
258void tst_QTextBoundaryFinder::sentenceBoundariesDefault_data()
259{
260 generateDataFromFile(fname: "data/SentenceBreakTest.txt");
261}
262
263void tst_QTextBoundaryFinder::sentenceBoundariesDefault()
264{
265 QFETCH(QString, testString);
266 QFETCH(QList<int>, expectedBreakPositions);
267
268 QScopedValueRollback<int> default_algorithm(qt_initcharattributes_default_algorithm_only);
269 qt_initcharattributes_default_algorithm_only++;
270
271 doTestData(testString, expectedBreakPositions, type: QTextBoundaryFinder::Sentence);
272}
273
274void tst_QTextBoundaryFinder::lineBoundariesDefault_data()
275{
276 generateDataFromFile(fname: "data/LineBreakTest.txt");
277}
278
279void tst_QTextBoundaryFinder::lineBoundariesDefault()
280{
281 QFETCH(QString, testString);
282 QFETCH(QList<int>, expectedBreakPositions);
283
284 QScopedValueRollback<int> default_algorithm(qt_initcharattributes_default_algorithm_only);
285 qt_initcharattributes_default_algorithm_only++;
286
287 expectedBreakPositions.prepend(t: 0); // ### QTBF generates a boundary at start of text
288 doTestData(testString, expectedBreakPositions, type: QTextBoundaryFinder::Line);
289}
290#endif // QT_BUILD_INTERNAL
291
292void tst_QTextBoundaryFinder::graphemeBoundaries_manual_data()
293{
294 QTest::addColumn<QString>(name: "testString");
295 QTest::addColumn<QList<int>>(name: "expectedBreakPositions");
296
297 {
298 // QTBUG-94951
299 QChar s[] = { QChar(0x2764), // U+2764 HEAVY BLACK HEART
300 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
301 QChar(0xD83D), QChar(0xDCF2), // U+1F4F2 MOBILE PHONE WITH RIGHTWARDS ARROW AT LEFT
302 QChar(0xD83D), QChar(0xDCE9), // U+1F4E9 ENVELOPE WITH DOWNWARDS ARROW ABOVE
303 };
304 QString testString(s, sizeof(s)/sizeof(s[0]));
305
306 QList<int> expectedBreakPositions{0, 2, 4, 6};
307 QTest::newRow(dataTag: "+EXTPICxEXT+EXTPIC+EXTPIC+") << testString << expectedBreakPositions;
308 }
309
310 {
311 QChar s[] = { QChar(0x2764), // U+2764 HEAVY BLACK HEART
312 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
313 QChar(0x2764), // U+2764 HEAVY BLACK HEART
314 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
315 };
316 QString testString(s, sizeof(s)/sizeof(s[0]));
317
318 QList<int> expectedBreakPositions{0, 2, 4};
319 QTest::newRow(dataTag: "+EXTPICxEXT+EXTPICxEXT+") << testString << expectedBreakPositions;
320 }
321
322 {
323 QChar s[] = { QChar(0x2764), // U+2764 HEAVY BLACK HEART
324 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
325 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
326 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
327 QChar(0x2764), // U+2764 HEAVY BLACK HEART
328 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
329 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
330 };
331 QString testString(s, sizeof(s)/sizeof(s[0]));
332
333 QList<int> expectedBreakPositions{0, 4, 7};
334 QTest::newRow(dataTag: "+EXTPICxEXTxEXTxEXT+EXTPICxEXTxEXT+") << testString << expectedBreakPositions;
335 }
336
337 {
338 QChar s[] = { QChar(0x2764), // U+2764 HEAVY BLACK HEART
339 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
340 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
341 QChar(0x200D), // U+200D ZERO WIDTH JOINER
342 QChar(0xD83D), QChar(0xDCF2), // U+1F4F2 MOBILE PHONE WITH RIGHTWARDS ARROW AT LEFT
343 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
344 };
345 QString testString(s, sizeof(s)/sizeof(s[0]));
346
347 QList<int> expectedBreakPositions{0, 7};
348 QTest::newRow(dataTag: "+EXTPICxEXTxEXTxZWJxEXTPICxEXTxEXT+") << testString << expectedBreakPositions;
349 }
350
351 {
352 QChar s[] = { QChar(0x2764), // U+2764 HEAVY BLACK HEART
353 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
354 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
355 QChar(0x200D), // U+200D ZERO WIDTH JOINER
356 QChar(0x0041), // U+0041 LATIN CAPITAL LETTER A
357 QChar(0xD83D), QChar(0xDCF2), // U+1F4F2 MOBILE PHONE WITH RIGHTWARDS ARROW AT LEFT
358 };
359 QString testString(s, sizeof(s)/sizeof(s[0]));
360
361 QList<int> expectedBreakPositions{0, 4, 5, 7};
362 QTest::newRow(dataTag: "+EXTPICxEXTxEXTxZWJ+Any+EXTPIC+") << testString << expectedBreakPositions;
363 }
364
365 {
366 QChar s[] = { QChar(0x2764), // U+2764 HEAVY BLACK HEART
367 QChar(0xFE0F), // U+FE0F VARIATION SELECTOR-16
368 QChar(0xD83C), QChar(0xDDEA), // U+1F1EA REGIONAL INDICATOR SYMBOL LETTER E
369 QChar(0xD83C), QChar(0xDDFA), // U+1F1FA REGIONAL INDICATOR SYMBOL LETTER U
370 QChar(0xD83C), QChar(0xDDEA), // U+1F1EA REGIONAL INDICATOR SYMBOL LETTER E
371 QChar(0xD83C), QChar(0xDDFA), // U+1F1FA REGIONAL INDICATOR SYMBOL LETTER U
372 QChar(0xD83C), QChar(0xDDEA), // U+1F1EA REGIONAL INDICATOR SYMBOL LETTER E
373 QChar(0x0041), // U+0041 LATIN CAPITAL LETTER A
374 };
375 QString testString(s, sizeof(s)/sizeof(s[0]));
376
377 QList<int> expectedBreakPositions{0, 2, 6, 10, 12, 13};
378 QTest::newRow(dataTag: "+EXTPICxEXT+RIxRI+RIxRI+RI+ANY+") << testString << expectedBreakPositions;
379 }
380}
381
382void tst_QTextBoundaryFinder::graphemeBoundaries_manual()
383{
384 QFETCH(QString, testString);
385 QFETCH(QList<int>, expectedBreakPositions);
386
387 doTestData(testString, expectedBreakPositions, type: QTextBoundaryFinder::Grapheme);
388}
389
390void tst_QTextBoundaryFinder::wordBoundaries_manual_data()
391{
392 QTest::addColumn<QString>(name: "testString");
393 QTest::addColumn<QList<int> >(name: "expectedBreakPositions");
394 QTest::addColumn<QList<int> >(name: "expectedStartPositions");
395 QTest::addColumn<QList<int> >(name: "expectedEndPositions");
396
397 {
398 QChar s[] = { 0x000D, 0x000A, 0x000A };
399 QString testString(s, sizeof(s)/sizeof(s[0]));
400 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
401 expectedBreakPositions << 0 << 2 << 3;
402
403 QTest::newRow(dataTag: "+CRxLF+LF+") << testString << expectedBreakPositions
404 << expectedStartPositions << expectedEndPositions;
405 }
406 {
407 QChar s[] = { 0x000D, 0x0308, 0x000A, 0x000A };
408 QString testString(s, sizeof(s)/sizeof(s[0]));
409 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
410 expectedBreakPositions << 0 << 1 << 2 << 3 << 4;
411
412 QTest::newRow(dataTag: "+CR+FE+LF+LF+") << testString << expectedBreakPositions
413 << expectedStartPositions << expectedEndPositions;
414 }
415 {
416 QString testString(QString::fromUtf8(str: "Aaa bbb ccc.\r\nDdd eee fff."));
417 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
418 expectedBreakPositions << 0 << 3 << 4 << 7 << 8 << 11 << 12 << 14 << 17 << 18 << 21 << 22 << 25 << 26;
419 expectedStartPositions << 0 << 4 << 8 << 14 << 18 << 22;
420 expectedEndPositions << 3 << 7 << 11 << 17 << 21 << 25;
421
422 QTest::newRow(dataTag: "words1") << testString << expectedBreakPositions
423 << expectedStartPositions << expectedEndPositions;
424 }
425 {
426 QString testString(QString::fromUtf8(str: "Hello (sad) world !"));
427 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
428 expectedBreakPositions << 0 << 5 << 6 << 7 << 10 << 11 << 12 << 17 << 18 << 19;
429 expectedStartPositions << 0 << 7 << 12;
430 expectedEndPositions << 5 << 10 << 17;
431
432 QTest::newRow(dataTag: "words2") << testString << expectedBreakPositions
433 << expectedStartPositions << expectedEndPositions;
434 }
435 {
436 QString testString(QString::fromUtf8(str: "mr.Hamster"));
437 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
438 expectedBreakPositions << 0 << 2 << 3 << 10;
439 expectedStartPositions << 0 << 3;
440 expectedEndPositions << 2 << 10;
441
442 QTest::newRow(dataTag: "words3") << testString << expectedBreakPositions
443 << expectedStartPositions << expectedEndPositions;
444 }
445 {
446 QString testString(QString::fromUtf8(str: "This is a sample buffer.Please test me . He's don't Le'Clerk."));
447 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
448 expectedBreakPositions << 0 << 4 << 5 << 7 << 8 << 9 << 10 << 11 << 12 << 13 << 14 << 20 << 21 << 27
449 << 28 << 34 << 35 << 39 << 40 << 42 << 43 << 44 << 45 << 46 << 47 << 48
450 << 49 << 53 << 54 << 59 << 60 << 68 << 69;
451 expectedStartPositions << 0 << 5 << 12 << 14 << 21 << 28 << 35 << 40 << 49 << 54 << 60;
452 expectedEndPositions << 4 << 7 << 13 << 20 << 27 << 34 << 39 << 42 << 53 << 59 << 68;
453
454 QTest::newRow(dataTag: "words4") << testString << expectedBreakPositions
455 << expectedStartPositions << expectedEndPositions;
456 }
457 {
458 // text with trailing space
459 QString testString(QString::fromUtf8(str: "Please test me. Finish "));
460 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
461 expectedBreakPositions << 0 << 6 << 7 << 11 << 12 << 14 << 15 << 16 << 22 << 23;
462 expectedStartPositions << 0 << 7 << 12 << 16;
463 expectedEndPositions << 6 << 11 << 14 << 22;
464
465 QTest::newRow(dataTag: "qtbug6498") << testString << expectedBreakPositions
466 << expectedStartPositions << expectedEndPositions;
467 }
468
469 // Sample Strings from WordBreakTest.html
470 {
471 QChar s[] = { 0x0063, 0x0061, 0x006E, 0x0027, 0x0074 };
472 QString testString(s, sizeof(s)/sizeof(s[0]));
473 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
474 expectedBreakPositions << 0 << 5;
475 expectedStartPositions << 0;
476 expectedEndPositions << 5;
477
478 QTest::newRow(dataTag: "ts 1") << testString << expectedBreakPositions
479 << expectedStartPositions << expectedEndPositions;
480 }
481 {
482 QChar s[] = { 0x0063, 0x0061, 0x006E, 0x2019, 0x0074 };
483 QString testString(s, sizeof(s)/sizeof(s[0]));
484 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
485 expectedBreakPositions << 0 << 5;
486 expectedStartPositions << 0;
487 expectedEndPositions << 5;
488
489 QTest::newRow(dataTag: "ts 2") << testString << expectedBreakPositions
490 << expectedStartPositions << expectedEndPositions;
491 }
492 {
493 QChar s[] = { 0x0061, 0x0062, 0x00AD, 0x0062, 0x0061 };
494 QString testString(s, sizeof(s)/sizeof(s[0]));
495 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
496 expectedBreakPositions << 0 << 5;
497 expectedStartPositions << 0;
498 expectedEndPositions << 5;
499
500 QTest::newRow(dataTag: "ts 3") << testString << expectedBreakPositions
501 << expectedStartPositions << expectedEndPositions;
502 }
503 {
504 QChar s[] = { 0x0061, 0x0024, 0x002D, 0x0033, 0x0034, 0x002C, 0x0035, 0x0036,
505 0x0037, 0x002E, 0x0031, 0x0034, 0x0025, 0x0062 };
506 QString testString(s, sizeof(s)/sizeof(s[0]));
507 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
508 expectedBreakPositions << 0 << 1 << 2 << 3 << 12 << 13 << 14;
509 expectedStartPositions << 0 << 3 << 13;
510 expectedEndPositions << 1 << 12 << 14;
511
512 QTest::newRow(dataTag: "ts 4") << testString << expectedBreakPositions
513 << expectedStartPositions << expectedEndPositions;
514 }
515 {
516 QChar s[] = { 0x0033, 0x0061 };
517 QString testString(s, sizeof(s)/sizeof(s[0]));
518 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
519 expectedBreakPositions << 0 << 2;
520 expectedStartPositions << 0;
521 expectedEndPositions << 2;
522
523 QTest::newRow(dataTag: "ts 5") << testString << expectedBreakPositions
524 << expectedStartPositions << expectedEndPositions;
525 }
526 {
527 QChar s[] = { 0x2060, 0x0063, 0x2060, 0x0061, 0x2060, 0x006E, 0x2060, 0x0027,
528 0x2060, 0x0074, 0x2060, 0x2060 };
529 QString testString(s, sizeof(s)/sizeof(s[0]));
530 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
531 expectedBreakPositions << 0 << 1 << 12;
532 expectedStartPositions << 1;
533 expectedEndPositions << 12;
534
535 QTest::newRow(dataTag: "ts 1e") << testString << expectedBreakPositions
536 << expectedStartPositions << expectedEndPositions;
537 }
538 {
539 QChar s[] = { 0x2060, 0x0063, 0x2060, 0x0061, 0x2060, 0x006E, 0x2060, 0x2019,
540 0x2060, 0x0074, 0x2060, 0x2060 };
541 QString testString(s, sizeof(s)/sizeof(s[0]));
542 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
543 expectedBreakPositions << 0 << 1 << 12;
544 expectedStartPositions << 1;
545 expectedEndPositions << 12;
546
547 QTest::newRow(dataTag: "ts 2e") << testString << expectedBreakPositions
548 << expectedStartPositions << expectedEndPositions;
549 }
550 {
551 QChar s[] = { 0x2060, 0x0061, 0x2060, 0x0062, 0x2060, 0x00AD, 0x2060, 0x0062,
552 0x2060, 0x0061, 0x2060, 0x2060 };
553 QString testString(s, sizeof(s)/sizeof(s[0]));
554 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
555 expectedBreakPositions << 0 << 1 << 12;
556 expectedStartPositions << 1;
557 expectedEndPositions << 12;
558
559 QTest::newRow(dataTag: "ts 3e") << testString << expectedBreakPositions
560 << expectedStartPositions << expectedEndPositions;
561 }
562 {
563 QChar s[] = { 0x2060, 0x0061, 0x2060, 0x0024, 0x2060, 0x002D, 0x2060, 0x0033,
564 0x2060, 0x0034, 0x2060, 0x002C, 0x2060, 0x0035, 0x2060, 0x0036,
565 0x2060, 0x0037, 0x2060, 0x002E, 0x2060, 0x0031, 0x2060, 0x0034,
566 0x2060, 0x0025, 0x2060, 0x0062, 0x2060, 0x2060 };
567 QString testString(s, sizeof(s)/sizeof(s[0]));
568 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
569 expectedBreakPositions << 0 << 1 << 3 << 5 << 7 << 25 << 27 << 30;
570 expectedStartPositions << 1 << 7 << 27;
571 expectedEndPositions << 3 << 25 << 30;
572
573 QTest::newRow(dataTag: "ts 4e") << testString << expectedBreakPositions
574 << expectedStartPositions << expectedEndPositions;
575 }
576 {
577 QChar s[] = { 0x2060, 0x0033, 0x2060, 0x0061, 0x2060, 0x2060 };
578 QString testString(s, sizeof(s)/sizeof(s[0]));
579 QList<int> expectedBreakPositions, expectedStartPositions, expectedEndPositions;
580 expectedBreakPositions << 0 << 1 << 6;
581 expectedStartPositions << 1;
582 expectedEndPositions << 6;
583
584 QTest::newRow(dataTag: "ts 5e") << testString << expectedBreakPositions
585 << expectedStartPositions << expectedEndPositions;
586 }
587}
588
589void tst_QTextBoundaryFinder::wordBoundaries_manual()
590{
591 QFETCH(QString, testString);
592 QFETCH(QList<int>, expectedBreakPositions);
593 QFETCH(QList<int>, expectedStartPositions);
594 QFETCH(QList<int>, expectedEndPositions);
595
596 doTestData(testString, expectedBreakPositions, type: QTextBoundaryFinder::Word);
597 doTestData(testString, expectedBreakPositions: expectedStartPositions, type: QTextBoundaryFinder::Word, reasons: QTextBoundaryFinder::StartOfItem);
598 doTestData(testString, expectedBreakPositions: expectedEndPositions, type: QTextBoundaryFinder::Word, reasons: QTextBoundaryFinder::EndOfItem);
599}
600
601void tst_QTextBoundaryFinder::sentenceBoundaries_manual_data()
602{
603 QTest::addColumn<QString>(name: "testString");
604 QTest::addColumn<QList<int> >(name: "expectedBreakPositions");
605
606 {
607 QChar s[] = { 0x000D, 0x000A, 0x000A };
608 QString testString(s, sizeof(s)/sizeof(s[0]));
609 QList<int> expectedBreakPositions;
610 expectedBreakPositions << 0 << 2 << 3;
611
612 QTest::newRow(dataTag: "+CRxLF+LF+") << testString << expectedBreakPositions;
613 }
614 {
615 QChar s[] = { 0x000D, 0x0308, 0x000A, 0x000A };
616 QString testString(s, sizeof(s)/sizeof(s[0]));
617 QList<int> expectedBreakPositions;
618 expectedBreakPositions << 0 << 1 << 3 << 4;
619
620 QTest::newRow(dataTag: "+CR+FExLF+LF+") << testString << expectedBreakPositions;
621 }
622 {
623 QString testString(QString::fromUtf8(str: "Aaa bbb ccc.\r\nDdd eee fff."));
624 QList<int> expectedBreakPositions;
625 expectedBreakPositions << 0 << 14 << 26;
626
627 QTest::newRow(dataTag: "data1") << testString << expectedBreakPositions;
628 }
629 {
630 QString testString(QString::fromUtf8(str: "Diga-nos qualé a sua opinião"));
631 QList<int> expectedBreakPositions;
632 expectedBreakPositions << 0 << 28;
633
634 QTest::newRow(dataTag: "data2") << testString << expectedBreakPositions;
635 }
636 {
637 QString testString(QString::fromUtf8(str: "mr.Hamster"));
638 QList<int> expectedBreakPositions;
639 expectedBreakPositions << 0 << 3 << 10;
640
641 QTest::newRow(dataTag: "data3") << testString << expectedBreakPositions;
642 }
643 {
644 QString testString(QString::fromUtf8(str: "Doing TEST, doing another test."));
645 QList<int> expectedBreakPositions;
646 expectedBreakPositions << 0 << 31;
647
648 QTest::newRow(dataTag: "data4") << testString << expectedBreakPositions;
649 }
650}
651
652void tst_QTextBoundaryFinder::sentenceBoundaries_manual()
653{
654 QFETCH(QString, testString);
655 QFETCH(QList<int>, expectedBreakPositions);
656
657 QVERIFY(expectedBreakPositions.size() >= 2);
658 QList<int> expectedStartPositions = expectedBreakPositions; expectedStartPositions.removeLast();
659 QList<int> expectedEndPositions = expectedBreakPositions; expectedEndPositions.removeFirst();
660
661 doTestData(testString, expectedBreakPositions, type: QTextBoundaryFinder::Sentence);
662 doTestData(testString, expectedBreakPositions: expectedStartPositions, type: QTextBoundaryFinder::Sentence, reasons: QTextBoundaryFinder::StartOfItem);
663 doTestData(testString, expectedBreakPositions: expectedEndPositions, type: QTextBoundaryFinder::Sentence, reasons: QTextBoundaryFinder::EndOfItem);
664}
665
666void tst_QTextBoundaryFinder::lineBoundaries_manual_data()
667{
668 QTest::addColumn<QString>(name: "testString");
669 QTest::addColumn<QList<int> >(name: "expectedBreakPositions");
670 QTest::addColumn<QList<int> >(name: "expectedMandatoryBreakPositions");
671
672 {
673 QString testString(QString::fromUtf8(str: "Aaa bbb ccc.\r\nDdd eee fff."));
674 QList<int> expectedBreakPositions, expectedMandatoryBreakPositions;
675 expectedBreakPositions << 0 << 4 << 8 << 14 << 18 << 22 << 26;
676 expectedMandatoryBreakPositions << 0 << 14 << 26;
677
678 QTest::newRow(dataTag: "data1") << testString << expectedBreakPositions
679 << expectedMandatoryBreakPositions;
680 }
681 {
682 QString testString(QString::fromUtf8(str: "Diga-nos qualé a sua opinião"));
683 QList<int> expectedBreakPositions, expectedMandatoryBreakPositions;
684 expectedBreakPositions << 0 << 5 << 9 << 15 << 17 << 21 << 28;
685 expectedMandatoryBreakPositions << 0 << 28;
686
687 QTest::newRow(dataTag: "data2") << testString << expectedBreakPositions
688 << expectedMandatoryBreakPositions;
689 }
690
691 {
692 QChar s[] = { 0x000D, 0x0308, 0x000A, 0x000A, 0x0020 };
693 QString testString(s, sizeof(s)/sizeof(s[0]));
694 QList<int> expectedBreakPositions, expectedMandatoryBreakPositions;
695 expectedBreakPositions << 0 << 1 << 3 << 4 << 5;
696 expectedMandatoryBreakPositions << 0 << 1 << 3 << 4 << 5;
697
698 QTest::newRow(dataTag: "x(CR)+(FE)x(LF)+(LF)+(SP)+") << testString << expectedBreakPositions
699 << expectedMandatoryBreakPositions;
700 }
701 {
702 QChar s[] = { 0x000A, 0x2E80, 0x0308, 0x0023, 0x0023 };
703 QString testString(s, sizeof(s)/sizeof(QChar));
704 QList<int> expectedBreakPositions, expectedMandatoryBreakPositions;
705 expectedBreakPositions << 0 << 1 << 3 << 5;
706 expectedMandatoryBreakPositions << 0 << 1 << 5;
707
708 QTest::newRow(dataTag: "x(LF)+(ID)x(CM)+(AL)x(AL)+") << testString << expectedBreakPositions
709 << expectedMandatoryBreakPositions;
710 }
711 {
712 QChar s[] = { 0x000A, 0x0308, 0x0023, 0x0023 };
713 QString testString(s, sizeof(s)/sizeof(QChar));
714 QList<int> expectedBreakPositions, expectedMandatoryBreakPositions;
715 expectedBreakPositions << 0 << 1 << 4;
716 expectedMandatoryBreakPositions << 0 << 1 << 4;
717
718 QTest::newRow(dataTag: "x(LF)+(CM)x(AL)x(AL)+") << testString << expectedBreakPositions
719 << expectedMandatoryBreakPositions;
720 }
721
722 {
723 QChar s[] = { 0x0061, 0x00AD, 0x0062, 0x0009, 0x0063, 0x0064 };
724 QString testString(s, sizeof(s)/sizeof(s[0]));
725 QList<int> expectedBreakPositions, expectedMandatoryBreakPositions;
726 expectedBreakPositions << 0 << 2 << 4 << 6;
727 expectedMandatoryBreakPositions << 0 << 6;
728
729 QTest::newRow(dataTag: "x(AL)x(BA)+(AL)x(BA)+(AL)x(AL)+") << testString << expectedBreakPositions
730 << expectedMandatoryBreakPositions;
731 }
732}
733
734void tst_QTextBoundaryFinder::lineBoundaries_manual()
735{
736 QFETCH(QString, testString);
737 QFETCH(QList<int>, expectedBreakPositions);
738 QFETCH(QList<int>, expectedMandatoryBreakPositions);
739
740 QVERIFY(expectedMandatoryBreakPositions.size() >= 2);
741 QList<int> expectedStartPositions = expectedMandatoryBreakPositions; expectedStartPositions.removeLast();
742 QList<int> expectedEndPositions = expectedMandatoryBreakPositions; expectedEndPositions.removeFirst();
743
744 doTestData(testString, expectedBreakPositions, type: QTextBoundaryFinder::Line);
745 doTestData(testString, expectedBreakPositions: expectedMandatoryBreakPositions, type: QTextBoundaryFinder::Line, reasons: QTextBoundaryFinder::MandatoryBreak);
746 doTestData(testString, expectedBreakPositions: expectedStartPositions, type: QTextBoundaryFinder::Line, reasons: QTextBoundaryFinder::StartOfItem);
747 doTestData(testString, expectedBreakPositions: expectedEndPositions, type: QTextBoundaryFinder::Line, reasons: QTextBoundaryFinder::EndOfItem);
748}
749
750Q_DECLARE_METATYPE(QTextBoundaryFinder)
751
752void tst_QTextBoundaryFinder::emptyText_data()
753{
754 QTest::addColumn<QTextBoundaryFinder>(name: "boundaryFinder");
755
756 QString empty;
757 QString notEmpty(QLatin1String("not empty"));
758 uchar attrs[11];
759
760 QTextBoundaryFinder invalidFinder(QTextBoundaryFinder::Word, empty);
761 QTest::newRow(dataTag: "empty1") << invalidFinder;
762 QTextBoundaryFinder finder(invalidFinder);
763 QTest::newRow(dataTag: "empty2") << finder;
764 finder = QTextBoundaryFinder(QTextBoundaryFinder::Grapheme, notEmpty);
765 finder = invalidFinder;
766 QTest::newRow(dataTag: "empty3") << finder;
767 QTest::newRow(dataTag: "empty4") << QTextBoundaryFinder(QTextBoundaryFinder::Word, notEmpty.constData(), 0, 0, 0);
768 QTest::newRow(dataTag: "empty5") << QTextBoundaryFinder(QTextBoundaryFinder::Word, notEmpty.constData(), 0, attrs, 11);
769 QTest::newRow(dataTag: "invalid1") << QTextBoundaryFinder(QTextBoundaryFinder::Word, 0, 10, 0, 0);
770 QTest::newRow(dataTag: "invalid2") << QTextBoundaryFinder(QTextBoundaryFinder::Word, 0, 10, attrs, 11);
771}
772
773void tst_QTextBoundaryFinder::emptyText()
774{
775 QFETCH(QTextBoundaryFinder, boundaryFinder);
776
777 QCOMPARE(boundaryFinder.position(), 0);
778 QCOMPARE(boundaryFinder.boundaryReasons(), QTextBoundaryFinder::NotAtBoundary);
779
780 boundaryFinder.toNextBoundary();
781 QCOMPARE(boundaryFinder.position(), -1);
782 QCOMPARE(boundaryFinder.boundaryReasons(), QTextBoundaryFinder::NotAtBoundary);
783}
784
785void tst_QTextBoundaryFinder::fastConstructor()
786{
787 QString text("Hello World");
788 QTextBoundaryFinder finder(QTextBoundaryFinder::Word, text.constData(), text.length(), /*buffer*/0, /*buffer size*/0);
789
790 QCOMPARE(finder.position(), 0);
791 QVERIFY(finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem);
792
793 finder.toNextBoundary();
794 QCOMPARE(finder.position(), 5);
795 QVERIFY(finder.boundaryReasons() & QTextBoundaryFinder::EndOfItem);
796
797 finder.toNextBoundary();
798 QCOMPARE(finder.position(), 6);
799 QVERIFY(finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem);
800
801 finder.toNextBoundary();
802 QCOMPARE(finder.position(), text.length());
803 QVERIFY(finder.boundaryReasons() & QTextBoundaryFinder::EndOfItem);
804
805 finder.toNextBoundary();
806 QCOMPARE(finder.position(), -1);
807 QCOMPARE(finder.boundaryReasons(), QTextBoundaryFinder::NotAtBoundary);
808}
809
810void tst_QTextBoundaryFinder::assignmentOperator()
811{
812 QString text(QLatin1String("Hello World"));
813
814 QTextBoundaryFinder invalidFinder;
815 QVERIFY(!invalidFinder.isValid());
816 QCOMPARE(invalidFinder.string(), QString());
817
818 QTextBoundaryFinder validFinder(QTextBoundaryFinder::Word, text);
819 QVERIFY(validFinder.isValid());
820 QCOMPARE(validFinder.string(), text);
821
822 QTextBoundaryFinder finder(QTextBoundaryFinder::Line, QLatin1String("dummy"));
823 QVERIFY(finder.isValid());
824
825 finder = invalidFinder;
826 QVERIFY(!finder.isValid());
827 QCOMPARE(finder.string(), QString());
828
829 finder = validFinder;
830 QVERIFY(finder.isValid());
831 QCOMPARE(finder.string(), text);
832}
833
834void tst_QTextBoundaryFinder::isAtSoftHyphen_data()
835{
836 QTest::addColumn<QString>(name: "testString");
837 QTest::addColumn<QList<int> >(name: "expectedBreakPositions");
838 QTest::addColumn<QList<int> >(name: "expectedSoftHyphenPositions");
839
840 {
841 QString testString = QString::fromUtf8(str: "I a-m break-able");
842 testString.replace(before: QLatin1Char('-'), after: QChar(QChar::SoftHyphen));
843 QList<int> expectedBreakPositions, expectedSoftHyphenPositions;
844 expectedBreakPositions << 0 << 2 << 4 << 6 << 12 << 16;
845 expectedSoftHyphenPositions << 4 << 12;
846
847 QTest::newRow(dataTag: "Soft Hyphen") << testString << expectedBreakPositions
848 << expectedSoftHyphenPositions;
849 }
850}
851
852void tst_QTextBoundaryFinder::isAtSoftHyphen()
853{
854 QFETCH(QString, testString);
855 QFETCH(QList<int>, expectedBreakPositions);
856 QFETCH(QList<int>, expectedSoftHyphenPositions);
857
858 doTestData(testString, expectedBreakPositions, type: QTextBoundaryFinder::Line);
859 doTestData(testString, expectedBreakPositions: expectedSoftHyphenPositions, type: QTextBoundaryFinder::Line, reasons: QTextBoundaryFinder::SoftHyphen);
860}
861
862#if QT_CONFIG(library)
863#include <qlibrary.h>
864#endif
865
866#define LIBTHAI_MAJOR 0
867typedef int (*th_brk_def) (const unsigned char*, int*, size_t);
868static th_brk_def th_brk = 0;
869
870static bool init_libthai()
871{
872#if QT_CONFIG(library)
873 static bool triedResolve = false;
874 if (!triedResolve) {
875 th_brk = (th_brk_def) QLibrary::resolve(fileName: "thai", verNum: (int)LIBTHAI_MAJOR, symbol: "th_brk");
876 triedResolve = true;
877 }
878#endif
879 return th_brk != 0;
880}
881
882void tst_QTextBoundaryFinder::thaiLineBreak()
883{
884 if (!init_libthai())
885 QSKIP("This test requires libThai-0.1.1x to be installed.");
886#if 0
887 // สวัสดีครับ นี่เป็นการงทดสอบตัวเอ
888 QTextCodec *codec = QTextCodec::codecForMib(2259);
889 QString text = codec->toUnicode(QByteArray("\xca\xc7\xd1\xca\xb4\xd5\xa4\xc3\xd1\xba\x20\xb9\xd5\xe8\xe0\xbb\xe7\xb9\xa1\xd2\xc3\xb7\xb4\xca\xcd\xba\xb5\xd1\xc7\xe0\xcd\xa7"));
890 QCOMPARE(text.length(), 32);
891
892 QTextBoundaryFinder finder(QTextBoundaryFinder::Line, text);
893 finder.setPosition(0);
894 QVERIFY(finder.isAtBoundary());
895 finder.setPosition(1);
896 QVERIFY(!finder.isAtBoundary());
897 finder.setPosition(2);
898 QVERIFY(!finder.isAtBoundary());
899 finder.setPosition(3);
900 QVERIFY(!finder.isAtBoundary());
901 finder.setPosition(4);
902 QVERIFY(!finder.isAtBoundary());
903 finder.setPosition(5);
904 QVERIFY(!finder.isAtBoundary());
905 finder.setPosition(6);
906 QVERIFY(finder.isAtBoundary());
907 finder.setPosition(7);
908 QVERIFY(finder.isAtBoundary());
909 finder.setPosition(8);
910 QVERIFY(!finder.isAtBoundary());
911 finder.setPosition(9);
912 QVERIFY(!finder.isAtBoundary());
913 finder.setPosition(10);
914 QVERIFY(!finder.isAtBoundary());
915 finder.setPosition(11);
916 QVERIFY(finder.isAtBoundary());
917 finder.setPosition(12);
918 QVERIFY(!finder.isAtBoundary());
919 finder.setPosition(13);
920 QVERIFY(!finder.isAtBoundary());
921 finder.setPosition(14);
922 QVERIFY(finder.isAtBoundary());
923 finder.setPosition(15);
924 QVERIFY(!finder.isAtBoundary());
925 finder.setPosition(16);
926 QVERIFY(!finder.isAtBoundary());
927 finder.setPosition(17);
928 QVERIFY(!finder.isAtBoundary());
929 finder.setPosition(18);
930 QVERIFY(finder.isAtBoundary());
931 finder.setPosition(19);
932 QVERIFY(!finder.isAtBoundary());
933 finder.setPosition(20);
934 QVERIFY(finder.isAtBoundary());
935 finder.setPosition(21);
936 QVERIFY(finder.isAtBoundary());
937 finder.setPosition(22);
938 QVERIFY(!finder.isAtBoundary());
939 finder.setPosition(23);
940 QVERIFY(!finder.isAtBoundary());
941 finder.setPosition(24);
942 QVERIFY(!finder.isAtBoundary());
943 finder.setPosition(25);
944 QVERIFY(finder.isAtBoundary());
945 finder.setPosition(26);
946 QVERIFY(finder.isAtBoundary());
947 for (int i = 27; i < 32; ++i) {
948 finder.setPosition(i);
949 QVERIFY(!finder.isAtBoundary());
950 }
951#endif
952}
953
954
955QTEST_MAIN(tst_QTextBoundaryFinder)
956#include "tst_qtextboundaryfinder.moc"
957

source code of qtbase/tests/auto/corelib/text/qtextboundaryfinder/tst_qtextboundaryfinder.cpp