TextEditor: add tests for semantic highlighter

Change-Id: Id64c933f01c0dbc0e077656b6f4260b93e124311
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2023-05-16 14:57:37 +02:00
parent 31090ded15
commit f235a72554
9 changed files with 365 additions and 1 deletions

View File

@@ -23,8 +23,12 @@ class SyntaxHighlighterPrivate
Q_DECLARE_PUBLIC(SyntaxHighlighter)
public:
SyntaxHighlighterPrivate()
: SyntaxHighlighterPrivate(TextEditorSettings::fontSettings())
{ }
SyntaxHighlighterPrivate(const FontSettings &fontSettings)
{
updateFormats(TextEditorSettings::fontSettings());
updateFormats(fontSettings);
}
QPointer<QTextDocument> doc;
@@ -76,6 +80,16 @@ void SyntaxHighlighter::delayedRehighlight()
rehighlight();
}
#ifdef WITH_TESTS
SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent, const FontSettings &fontsettings)
: QObject(parent), d_ptr(new SyntaxHighlighterPrivate(fontsettings))
{
d_ptr->q_ptr = this;
if (parent)
setDocument(parent);
}
#endif
void SyntaxHighlighterPrivate::applyFormatChanges(int from, int charsRemoved, int charsAdded)
{
bool formatsChanged = false;

View File

@@ -91,6 +91,11 @@ private:
void delayedRehighlight();
QScopedPointer<SyntaxHighlighterPrivate> d_ptr;
#ifdef WITH_TESTS
friend class tst_highlighter;
SyntaxHighlighter(QTextDocument *parent, const FontSettings &fontsettings);
#endif
};
} // namespace TextEditor

View File

@@ -20,6 +20,7 @@ add_subdirectory(qml)
add_subdirectory(runextensions)
add_subdirectory(sdktool)
add_subdirectory(solutions)
add_subdirectory(texteditor)
add_subdirectory(toolchaincache)
add_subdirectory(tracing)
add_subdirectory(treeviewfind)

View File

@@ -23,6 +23,7 @@ Project {
"runextensions/runextensions.qbs",
"sdktool/sdktool.qbs",
"solutions/solutions.qbs",
"texteditor/texteditor.qbs",
"toolchaincache/toolchaincache.qbs",
"tracing/tracing.qbs",
"treeviewfind/treeviewfind.qbs",

View File

@@ -0,0 +1 @@
add_subdirectory(highlighter)

View File

@@ -0,0 +1,4 @@
add_qtc_test(tst_highlighter
DEPENDS TextEditor Utils Qt::Widgets
SOURCES tst_highlighter.cpp
)

View File

@@ -0,0 +1,15 @@
import qbs
QtcAutotest {
Depends { name: "TextEditor" }
Depends { name: "Utils" }
Depends { name: "Qt.widgets" } // For QTextDocument & friends
name: "Highlighter autotest"
Group {
name: "Source Files"
files: "tst_highlighter.cpp"
}
}

View File

@@ -0,0 +1,317 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef WITH_TESTS
#define WITH_TESTS
#endif
#include <texteditor/semantichighlighter.h>
#include <texteditor/syntaxhighlighter.h>
#include <texteditor/texteditorsettings.h>
#include <QObject>
#include <QTextBlock>
#include <QTextDocument>
#include <QtTest>
namespace TextEditor {
class tst_highlighter: public QObject
{
Q_OBJECT
private slots:
void init_testCase();
void init();
void test_setExtraAdditionalFormats();
void test_clearExtraFormats();
void test_incrementalApplyAdditionalFormats();
void cleanup();
private:
QTextDocument *doc = nullptr;
SyntaxHighlighter *highlighter = nullptr;
FontSettings fontsettings;
QHash<int, QTextCharFormat> formatHash;
QTextCharFormat whitespaceFormat;
};
void tst_highlighter::init_testCase()
{
QTextCharFormat c0;
c0.setFontItalic(true);
formatHash[0] = c0;
QTextCharFormat c1;
c1.setFontOverline(true);
formatHash[1] = c1;
}
void tst_highlighter::init()
{
const QString text =
R"(First
Second with spaces
Last)";
doc = new QTextDocument();
doc->setPlainText(text);
highlighter = new SyntaxHighlighter(doc, fontsettings);
}
static const HighlightingResults &highlightingResults()
{
static HighlightingResults results{HighlightingResult(),
HighlightingResult(1, 1, 5, 0),
HighlightingResult(2, 4, 7, 0),
HighlightingResult(2, 17, 5, 1),
HighlightingResult(4, 1, 8, 0),
HighlightingResult(6, 1, 8, 1)};
return results;
}
void tst_highlighter::test_setExtraAdditionalFormats()
{
QCOMPARE(doc->blockCount(), 4);
SemanticHighlighter::setExtraAdditionalFormats(highlighter, highlightingResults(), formatHash);
for (auto block = doc->firstBlock(); block.isValid(); block = block.next()) {
QVERIFY(block.blockNumber() < 4);
QVERIFY(block.layout());
auto formats = block.layout()->formats();
switch (block.blockNumber()) {
case 0: // First
QCOMPARE(formats.size(), 1);
QCOMPARE(formats.at(0).format.fontItalic(), true);
QCOMPARE(formats.at(0).format.fontOverline(), false);
QCOMPARE(formats.at(0).start, 0);
QCOMPARE(formats.at(0).length, 5);
break;
case 1: // Second with spaces
QCOMPARE(formats.size(), 4);
QCOMPARE(formats.at(0).format.fontItalic(), false);
QCOMPARE(formats.at(0).format.fontOverline(), false);
QCOMPARE(formats.at(0).start, 6);
QCOMPARE(formats.at(0).length, 1);
QCOMPARE(formats.at(1).format.fontItalic(), false);
QCOMPARE(formats.at(1).format.fontOverline(), false);
QCOMPARE(formats.at(1).start, 11);
QCOMPARE(formats.at(1).length, 1);
QCOMPARE(formats.at(2).format.fontItalic(), true);
QCOMPARE(formats.at(2).format.fontOverline(), false);
QCOMPARE(formats.at(2).start, 3);
QCOMPARE(formats.at(2).length, 7);
QCOMPARE(formats.at(3).format.fontItalic(), false);
QCOMPARE(formats.at(3).format.fontOverline(), true);
QCOMPARE(formats.at(3).start, 16);
QCOMPARE(formats.at(3).length, 3);
break;
case 2: //
QCOMPARE(formats.size(), 1);
QCOMPARE(formats.at(0).format.fontItalic(), false);
QCOMPARE(formats.at(0).format.fontOverline(), true);
QCOMPARE(formats.at(0).start, 0);
QCOMPARE(formats.at(0).length, 1);
break;
case 3: // Last
QCOMPARE(formats.size(), 2);
QCOMPARE(formats.at(0).format.fontItalic(), false);
QCOMPARE(formats.at(0).format.fontOverline(), true);
QCOMPARE(formats.at(0).start, 0);
QCOMPARE(formats.at(0).length, 1);
QCOMPARE(formats.at(1).format.fontItalic(), true);
QCOMPARE(formats.at(1).format.fontOverline(), false);
QCOMPARE(formats.at(1).start, 0);
QCOMPARE(formats.at(1).length, 5);
break;
}
}
}
void tst_highlighter::test_clearExtraFormats()
{
QCOMPARE(doc->blockCount(), 4);
SemanticHighlighter::setExtraAdditionalFormats(highlighter, highlightingResults(), formatHash);
QTextBlock firstBlock = doc->findBlockByNumber(0);
QTextBlock spacesLineBlock = doc->findBlockByNumber(1);
QTextBlock emptyLineBlock = doc->findBlockByNumber(2);
QTextBlock lastBlock = doc->findBlockByNumber(3);
highlighter->clearExtraFormats(emptyLineBlock);
QVERIFY(emptyLineBlock.layout()->formats().isEmpty());
highlighter->clearExtraFormats(spacesLineBlock);
auto formats = spacesLineBlock.layout()->formats();
// the spaces are not extra formats and should remain when clearing extra formats
QCOMPARE(formats.size(), 2);
QCOMPARE(formats.at(0).format.fontItalic(), false);
QCOMPARE(formats.at(0).format.fontOverline(), false);
QCOMPARE(formats.at(0).start, 6);
QCOMPARE(formats.at(0).length, 1);
QCOMPARE(formats.at(1).format.fontItalic(), false);
QCOMPARE(formats.at(1).format.fontOverline(), false);
QCOMPARE(formats.at(1).start, 11);
QCOMPARE(formats.at(1).length, 1);
// first and last should be untouched
formats = firstBlock.layout()->formats();
QCOMPARE(formats.size(), 1);
QCOMPARE(formats.at(0).format.fontItalic(), true);
QCOMPARE(formats.at(0).format.fontOverline(), false);
QCOMPARE(formats.at(0).start, 0);
QCOMPARE(formats.at(0).length, 5);
formats = lastBlock.layout()->formats();
QCOMPARE(formats.size(), 2);
QCOMPARE(formats.at(0).format.fontItalic(), false);
QCOMPARE(formats.at(0).format.fontOverline(), true);
QCOMPARE(formats.at(0).start, 0);
QCOMPARE(formats.at(0).length, 1);
QCOMPARE(formats.at(1).format.fontItalic(), true);
QCOMPARE(formats.at(1).format.fontOverline(), false);
QCOMPARE(formats.at(1).start, 0);
QCOMPARE(formats.at(1).length, 5);
highlighter->clearAllExtraFormats();
QVERIFY(firstBlock.layout()->formats().isEmpty());
QVERIFY(emptyLineBlock.layout()->formats().isEmpty());
formats = spacesLineBlock.layout()->formats();
QCOMPARE(formats.size(), 2);
QCOMPARE(formats.at(0).format.fontItalic(), false);
QCOMPARE(formats.at(0).format.fontOverline(), false);
QCOMPARE(formats.at(0).start, 6);
QCOMPARE(formats.at(0).length, 1);
QCOMPARE(formats.at(1).format.fontItalic(), false);
QCOMPARE(formats.at(1).format.fontOverline(), false);
QCOMPARE(formats.at(1).start, 11);
QCOMPARE(formats.at(1).length, 1);
QVERIFY(lastBlock.layout()->formats().isEmpty());
}
void tst_highlighter::test_incrementalApplyAdditionalFormats()
{
const HighlightingResults newResults{HighlightingResult(),
HighlightingResult(1, 1, 5, 0),
HighlightingResult(2, 4, 7, 0),
HighlightingResult(4, 1, 8, 0),
HighlightingResult(6, 1, 8, 1)};
QCOMPARE(doc->blockCount(), 4);
QTextBlock firstBlock = doc->findBlockByNumber(0);
QTextBlock spacesLineBlock = doc->findBlockByNumber(1);
QTextBlock emptyLineBlock = doc->findBlockByNumber(2);
QTextBlock lastBlock = doc->findBlockByNumber(3);
QFutureInterface<HighlightingResult> fiOld;
fiOld.reportResults(highlightingResults());
SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter,
fiOld.future(),
2,
0,
formatHash);
auto formats = firstBlock.layout()->formats();
QVERIFY(formats.isEmpty());
SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter,
fiOld.future(),
0,
2,
formatHash);
formats = firstBlock.layout()->formats();
QCOMPARE(formats.at(0).format.fontItalic(), true);
QCOMPARE(formats.at(0).format.fontOverline(), false);
QCOMPARE(formats.at(0).start, 0);
QCOMPARE(formats.at(0).length, 5);
formats = spacesLineBlock.layout()->formats();
QCOMPARE(formats.size(), 2);
QCOMPARE(formats.at(0).format.fontItalic(), false);
QCOMPARE(formats.at(0).format.fontOverline(), false);
QCOMPARE(formats.at(0).start, 6);
QCOMPARE(formats.at(0).length, 1);
QCOMPARE(formats.at(1).format.fontItalic(), false);
QCOMPARE(formats.at(1).format.fontOverline(), false);
QCOMPARE(formats.at(1).start, 11);
QCOMPARE(formats.at(1).length, 1);
SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter,
fiOld.future(),
3,
6,
formatHash);
formats = firstBlock.layout()->formats();
QCOMPARE(formats.at(0).format.fontItalic(), true);
QCOMPARE(formats.at(0).format.fontOverline(), false);
QCOMPARE(formats.at(0).start, 0);
QCOMPARE(formats.at(0).length, 5);
formats = lastBlock.layout()->formats();
QCOMPARE(formats.size(), 2);
QCOMPARE(formats.at(0).format.fontItalic(), false);
QCOMPARE(formats.at(0).format.fontOverline(), true);
QCOMPARE(formats.at(0).start, 0);
QCOMPARE(formats.at(0).length, 1);
QCOMPARE(formats.at(1).format.fontItalic(), true);
QCOMPARE(formats.at(1).format.fontOverline(), false);
QCOMPARE(formats.at(1).start, 0);
QCOMPARE(formats.at(1).length, 5);
QFutureInterface<HighlightingResult> fiNew;
fiNew.reportResults(newResults);
SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter,
fiNew.future(),
0,
3,
formatHash);
// should still contain the highlight from oldResults
formats = emptyLineBlock.layout()->formats();
QCOMPARE(formats.size(), 1);
QCOMPARE(formats.at(0).format.fontItalic(), false);
QCOMPARE(formats.at(0).format.fontOverline(), true);
QCOMPARE(formats.at(0).start, 0);
QCOMPARE(formats.at(0).length, 1);
SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter,
fiNew.future(),
3,
4,
formatHash);
// should have no results since the new results do not contain a highlight at that position
formats = emptyLineBlock.layout()->formats();
QVERIFY(formats.isEmpty());
}
void tst_highlighter::cleanup()
{
delete doc;
doc = nullptr;
highlighter = nullptr;
}
} // namespace TextEditor
QTEST_MAIN(TextEditor::tst_highlighter)
#include "tst_highlighter.moc"

View File

@@ -0,0 +1,6 @@
import qbs
Project {
name: "TextEditor autotests"
references: [ "highlighter/highlighter.qbs" ]
}