diff --git a/.github/workflows/build_cmake.yml b/.github/workflows/build_cmake.yml
index c24165d..2b0d62f 100644
--- a/.github/workflows/build_cmake.yml
+++ b/.github/workflows/build_cmake.yml
@@ -70,7 +70,7 @@ jobs:
cmakeVersion: ${{ env.CMAKE_VERSION }}
ninjaVersion: ${{ env.NINJA_VERSION }}
- - name: Install system libs
+ - name: Install dependencies
shell: cmake -P {0}
run: |
if ("${{ runner.os }}" STREQUAL "Linux")
@@ -78,7 +78,13 @@ jobs:
COMMAND sudo apt update
)
execute_process(
- COMMAND sudo apt install libgl1-mesa-dev
+ COMMAND sudo apt install
+ # build dependencies
+ libgl1-mesa-dev libgtest-dev
+ # runtime dependencies for tests (Qt is downloaded outside package manager,
+ # thus minimal dependencies must be installed explicitly)
+ libsecret-1-0 libxcb-cursor0 libxcb-icccm4 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-render0
+ libxcb-shape0 libxcb-shm0 libxcb-sync1 libxcb-xfixes0 libxcb-xkb1 libxkbcommon-x11-0 xvfb
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)
@@ -258,6 +264,11 @@ jobs:
name: ${{ env.PLUGIN_NAME }}-origin-json
path: ./build/build/${{ env.PLUGIN_NAME }}.json
+ - name: Run unit tests
+ if: matrix.config.os == 'ubuntu-latest'
+ run: |
+ xvfb-run ./build/build/test/QodeAssistTest
+
update_json:
if: contains(github.ref, 'tags/v')
runs-on: ubuntu-latest
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7cc1f76..83ea3e4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,6 +12,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
find_package(QtCreator REQUIRED COMPONENTS Core)
find_package(Qt6 COMPONENTS Core Gui Quick Widgets Network REQUIRED)
+find_package(GTest)
add_definitions(
-DQODEASSIST_QT_CREATOR_VERSION_MAJOR=${QODEASSIST_QT_CREATOR_VERSION_MAJOR}
@@ -24,6 +25,9 @@ add_subdirectory(settings)
add_subdirectory(logger)
add_subdirectory(ChatView)
add_subdirectory(context)
+if(GTest_FOUND)
+ add_subdirectory(test)
+endif()
add_qtc_plugin(QodeAssist
PLUGIN_DEPENDS
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..6702761
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_executable(QodeAssistTest
+ DocumentContextReaderTest.cpp
+ unittest_main.cpp
+)
+
+target_link_libraries(QodeAssistTest PRIVATE
+ Qt::Core
+ GTest::GTest
+ GTest::Main
+ QtCreator::LanguageClient
+ Context
+)
+
+add_test(NAME QodeAssistTest COMMAND QodeAssistTest)
diff --git a/test/DocumentContextReaderTest.cpp b/test/DocumentContextReaderTest.cpp
new file mode 100644
index 0000000..d3b074a
--- /dev/null
+++ b/test/DocumentContextReaderTest.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2025 Povilas Kanapickas
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#include "context/DocumentContextReader.hpp"
+
+#include
+#include
+
+using namespace QodeAssist::Context;
+
+std::ostream &operator<<(std::ostream &out, const QString &value)
+{
+ out << value.toStdString();
+ return out;
+}
+
+class DocumentContextReaderTest : public QObject, public testing::Test
+{
+ Q_OBJECT
+
+protected:
+ QTextDocument *createTestDocument(const QString &text)
+ {
+ auto *doc = new QTextDocument(this);
+ doc->setPlainText(text);
+ return doc;
+ }
+
+ DocumentContextReader createTestReader(const QString &text)
+ {
+ return DocumentContextReader(createTestDocument(text), "text/python", "/path/to/file");
+ }
+};
+
+TEST_F(DocumentContextReaderTest, testGetLineText)
+{
+ auto reader = createTestReader("Line 1\nLine 2\nLine 3");
+
+ EXPECT_EQ(reader.getLineText(0), "Line 1");
+ EXPECT_EQ(reader.getLineText(1), "Line 2");
+ EXPECT_EQ(reader.getLineText(2), "Line 3");
+ EXPECT_EQ(reader.getLineText(0, 4), "Line");
+}
+
+TEST_F(DocumentContextReaderTest, testGetContextBefore)
+{
+ auto reader = createTestReader("Line 1\nLine 2\nLine 3\nLine 4\nLine 5");
+
+ EXPECT_EQ(reader.getContextBefore(2, -1, 2), "Line 1\nLine 2\nLine 3");
+ EXPECT_EQ(reader.getContextBefore(4, -1, 3), "Line 2\nLine 3\nLine 4\nLine 5");
+}
+
+TEST_F(DocumentContextReaderTest, testGetContextAfter)
+{
+ auto reader = createTestReader("Line 1\nLine 2\nLine 3\nLine 4\nLine 5");
+
+ EXPECT_EQ(reader.getContextAfter(0, -1, 2), "Line 2\nLine 3");
+ EXPECT_EQ(reader.getContextAfter(2, -1, 2), "Line 4\nLine 5");
+}
+
+TEST_F(DocumentContextReaderTest, testGetContextBeforeWithCopyright)
+{
+ auto reader = createTestReader("/* Copyright (C) 2024 */\nLine 1\nLine 2\nLine 3\nLine 4");
+
+ // Test getting context before with copyright header
+ EXPECT_EQ(reader.getContextBefore(2, -1, 2), "Line 1\nLine 2");
+
+ // Test getting context before skipping copyright
+ EXPECT_EQ(reader.getContextBefore(3, 0, 2), "Line 1\nLine 2\n");
+}
+
+TEST_F(DocumentContextReaderTest, testGetContextAfterWithCopyright)
+{
+ auto reader = createTestReader("/* Copyright (C) 2024 */\nLine 1\nLine 2\nLine 3\nLine 4");
+
+ // Test getting context after copyright header
+ EXPECT_EQ(reader.getContextAfter(0, -1, 2), "Line 1\nLine 2");
+
+ // Test getting context after with copyright skipped
+ EXPECT_EQ(reader.getContextAfter(1, 0, 2), "Line 2\n");
+}
+
+TEST_F(DocumentContextReaderTest, testReadWholeFile)
+{
+ auto reader = createTestReader("Line 1\nLine 2\nLine 3\nLine 4\nLine 5");
+
+ EXPECT_EQ(reader.readWholeFileBefore(2, -1), "Line 1\nLine 2\nLine 3");
+ EXPECT_EQ(reader.readWholeFileAfter(2, -1), "Line 3\nLine 4\nLine 5");
+}
+
+TEST_F(DocumentContextReaderTest, testReadWholeFileWithCopyright)
+{
+ auto reader = createTestReader("/* Copyright (C) 2024 */\nLine 1\nLine 2\nLine 3\nLine 4");
+
+ EXPECT_EQ(reader.readWholeFileBefore(2, -1), "Line 1\nLine 2");
+ EXPECT_EQ(reader.readWholeFileAfter(2, -1), "Line 2\nLine 3\nLine 4");
+
+ EXPECT_EQ(reader.readWholeFileBefore(2, 0), "Line 1\n");
+ EXPECT_EQ(reader.readWholeFileAfter(2, 0), "Line 2\nLine 3\n");
+}
+
+TEST_F(DocumentContextReaderTest, testReadWholeFileWithMultilineCopyright)
+{
+ auto reader = createTestReader(
+ "/*\n * Copyright (C) 2024\n * \n * This file is part of QodeAssist.\n */\n"
+ "Line 1\nLine 2");
+
+ EXPECT_EQ(reader.readWholeFileBefore(6, -1), "Line 1\nLine 2");
+ EXPECT_EQ(reader.readWholeFileAfter(5, -1), "Line 1\nLine 2");
+
+ EXPECT_EQ(reader.readWholeFileBefore(6, 4), "Line 1\nLine");
+ EXPECT_EQ(reader.readWholeFileAfter(5, 4), "Line 1\nLine");
+}
+
+TEST_F(DocumentContextReaderTest, testFindCopyrightSingleLine)
+{
+ auto reader = createTestReader("/* Copyright (C) 2024 */\nCode line 1\nCode line 2");
+
+ auto info = reader.findCopyright();
+ ASSERT_TRUE(info.found);
+ EXPECT_EQ(info.startLine, 0);
+ EXPECT_EQ(info.endLine, 0);
+}
+
+TEST_F(DocumentContextReaderTest, testFindCopyrightMultiLine)
+{
+ auto reader = createTestReader(
+ "/*\n * Copyright (C) 2024\n * \n * This file is part of QodeAssist.\n */\nCode line 1");
+
+ auto info = reader.findCopyright();
+ ASSERT_TRUE(info.found);
+ EXPECT_EQ(info.startLine, 0);
+ EXPECT_EQ(info.endLine, 4);
+}
+
+TEST_F(DocumentContextReaderTest, testFindCopyrightMultipleBlocks)
+{
+ auto reader = createTestReader("/* Copyright 2023 */\n\n/* Copyright 2024 */\nCode");
+
+ auto info = reader.findCopyright();
+ ASSERT_TRUE(info.found);
+ EXPECT_EQ(info.startLine, 0);
+ EXPECT_EQ(info.endLine, 0);
+}
+
+TEST_F(DocumentContextReaderTest, testFindCopyrightNoCopyright)
+{
+ auto reader = createTestReader("/* Just a comment */\nCode line 1");
+
+ auto info = reader.findCopyright();
+ ASSERT_TRUE(!info.found);
+ EXPECT_EQ(info.startLine, -1);
+ EXPECT_EQ(info.endLine, -1);
+}
+
+TEST_F(DocumentContextReaderTest, testGetContextBetween)
+{
+ auto reader = createTestReader("Line 1\nLine 2\nLine 3\nLine 4\nLine 5");
+
+ EXPECT_EQ(reader.getContextBetween(1, 3, -1), "Line 2\nLine 3\nLine 4");
+ EXPECT_EQ(reader.getContextBetween(0, 2, 4), "Line 1\nLine 2\nLine");
+}
+
+#include "DocumentContextReaderTest.moc"
diff --git a/test/unittest_main.cpp b/test/unittest_main.cpp
new file mode 100644
index 0000000..2ed6164
--- /dev/null
+++ b/test/unittest_main.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 Povilas Kanapickas
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#include
+#include
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication application(argc, argv);
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}