diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..2c4e446
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,10 @@
+BasedOnStyle: Chromium
+ColumnLimit: 100
+AllowShortLoopsOnASingleLine: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+BreakConstructorInitializersBeforeComma: true
+AlignConsecutiveAssignments: true
+AllowShortFunctionsOnASingleLine: true
+BraceWrapping:
+ AfterControlStatement: Always
+InsertBraces: true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..54fe2f4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,84 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+*.qbs.user*
+CMakeLists.txt.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+
+# Directories with generated files
+.moc/
+.obj/
+.pch/
+.rcc/
+.uic/
+/build*/
+_build*/
+_output/*
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..061e3b3
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,9 @@
+[submodule "libs/GenericCore"]
+ path = libs/GenericCore
+ url = ssh://wococo1@git.working-copy.org:46100/bent/GenericQtClientCore.git
+[submodule "UIs/GenericWidgets"]
+ path = UIs/GenericWidgets
+ url = ssh://wococo1@git.working-copy.org:46100/bent/GenericQtClientWidgets.git
+[submodule "libs/3rdParty/rapidcsv"]
+ path = libs/3rdParty/rapidcsv
+ url = https://github.com/d99kris/rapidcsv.git
diff --git a/ApplicationConfig.h.in b/ApplicationConfig.h.in
new file mode 100644
index 0000000..c86fd15
--- /dev/null
+++ b/ApplicationConfig.h.in
@@ -0,0 +1,2 @@
+#define APPLICATION_NAME "${PROJECT_NAME}"
+#define APPLICATION_VERSION "${PROJECT_VERSION}"
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..7d85167
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,15 @@
+# Changelog
+
+## 0.2 - 2026-01-14
+
+### Added
+
+- Displaying editable table model (sortable by column)
+- Modifying model data can be un-/redone
+- Data is stored in JSON file and automatically loaded on application start
+- Data can be imported/exported from/into CSV file
+- Model rows containing specific data can be selected via "Find item(s)" dialog
+
+## 0.1 - 2025-11-01
+
+A simple Qt application separated into an UI frontend and backend core. With installer (for Linux for now) and option to trigger updater from within the application.
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..14d2423
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,51 @@
+cmake_minimum_required(VERSION 3.16)
+
+set(TARGET_APP "GenericQtClient")
+project(${TARGET_APP} VERSION 0.2.0 LANGUAGES CXX)
+
+enable_testing()
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+add_subdirectory(libs/GenericCore)
+set (CORE_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/GenericCore)
+
+configure_file(ApplicationConfig.h.in ApplicationConfig.h)
+
+#Frontend applications
+add_subdirectory(UIs/GenericWidgets)
+
+### Tests
+add_subdirectory(tests/GenericCoreTests)
+
+### Qt installer
+set(PACKAGE_FOLDER "genericQtClient")
+set(TARGET_PACKAGE "org.working_copy.${PACKAGE_FOLDER}")
+set(CONFIG_REPO_URL ".../${PACKAGE_FOLDER}")
+set(PUSH_REPO_URL ".../${PACKAGE_FOLDER}")
+string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
+set(PLATFORM "linux")
+
+set(BASH_COMMAND)
+
+### quick fix for config.xml.in and installscript.qs.in:
+set(ApplicationsDir @ApplicationsDir@)
+set(TargetDir @TargetDir@)
+set(DesktopDir @DesktopDir@)
+set(HomeDir @HomeDir@)
+### end of quick fix
+
+configure_file(installer/config/config.xml.in installer/config/config.xml)
+configure_file(installer/packages/defaultPackage/meta/package.xml.in installer/packages/${TARGET_PACKAGE}/meta/package.xml)
+configure_file(installer/packages/defaultPackage/meta/installscript.qs.in installer/packages/${TARGET_PACKAGE}/meta/installscript.qs)
+# configure_file(installer/packages/defaultPackage/meta/license.txt.in installer/packages/${TARGET_PACKAGE}/meta/license.txt)
+
+# add_custom_command(
+# OUTPUT installer/config/config.xml
+# COMMAND ${BASH_COMMAND} ${CMAKE_CURRENT_SOURCE_DIR}/installer/script.sh ${PROJECT_VERSION} linux ${TARGET_APP} ${TARGET_PACKAGE}
+# )
+
+# add_custom_target(installer ALL
+# DEPENDS installer/config/config.xml
+# )
diff --git a/README.md b/README.md
index a9e2eaf..c008bb9 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,19 @@
# GenericQtClient
+This is a Qt application which can be used as a starting point for new software projects.
+
+Common features most Qt software clients need will be already implemented and can be easily configured for the specific needs.
+
+## Implemented features:
+- Separated UI frontend and backend core (in its own git submodules)
+- Using Qt model/view framework with QT undo framework
+- Saving/Loading JSON files
+- CSV import/export
+- installable and updateable via Qt updater framework
+ - only linux for now
+ - Qt 6 libraries must be installed on the machine to run
+
+## Coming features:
+- REST client
+- Extensive use of sorting and filtering models to display data in different ways
+- ...
diff --git a/UIs/GenericWidgets b/UIs/GenericWidgets
new file mode 160000
index 0000000..c83ba2d
--- /dev/null
+++ b/UIs/GenericWidgets
@@ -0,0 +1 @@
+Subproject commit c83ba2da9d43064e63e060fd9f40da17e30430e5
diff --git a/assets/icons/GenericQtClient.png b/assets/icons/GenericQtClient.png
new file mode 100644
index 0000000..a9b73d9
Binary files /dev/null and b/assets/icons/GenericQtClient.png differ
diff --git a/assets/icons/feature.png b/assets/icons/feature.png
new file mode 100644
index 0000000..a9b73d9
Binary files /dev/null and b/assets/icons/feature.png differ
diff --git a/assets/icons/icons.qrc b/assets/icons/icons.qrc
new file mode 100644
index 0000000..d51a60e
--- /dev/null
+++ b/assets/icons/icons.qrc
@@ -0,0 +1,7 @@
+
+
+ software-application.png
+ feature.png
+ no-picture-taking.png
+
+
diff --git a/assets/icons/no-picture-taking.png b/assets/icons/no-picture-taking.png
new file mode 100644
index 0000000..2ae8799
Binary files /dev/null and b/assets/icons/no-picture-taking.png differ
diff --git a/assets/icons/software-application.png b/assets/icons/software-application.png
new file mode 100644
index 0000000..2edb87d
Binary files /dev/null and b/assets/icons/software-application.png differ
diff --git a/assets/icons/urls.txt b/assets/icons/urls.txt
new file mode 100644
index 0000000..7442630
--- /dev/null
+++ b/assets/icons/urls.txt
@@ -0,0 +1,5 @@
+software-application.png:
+https://www.flaticon.com/free-icon/software-application_5063917
+
+feature.png:
+https://www.flaticon.com/free-icon/feature_1085784
diff --git a/installer/.env b/installer/.env
new file mode 100644
index 0000000..568103f
--- /dev/null
+++ b/installer/.env
@@ -0,0 +1,4 @@
+# ssh url of the packages directory of the repo
+PROJECT_FOLDER=""
+export CONFIG_REPO_URL="/${PROJECT_FOLDER}"
+export PUSH_REPO_URL="/${PROJECT_FOLDER}"
diff --git a/installer/build.sh b/installer/build.sh
new file mode 100755
index 0000000..c88c7c1
--- /dev/null
+++ b/installer/build.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+echo amount of arguments: $#
+
+if [[ $# -ne 4 ]]; then
+ echo "Build script has to be called with exactly two arguments."
+ echo "The version number (Major.Minor.Patch) as its first argument."
+ echo "... and the OS (linux, windows or macos) as the second argument"
+ echo "TODO: DOCUMENT ARGUMENTS 3 & 4!!!"
+ echo "Aborting..."
+ exit 1
+fi
+
+VERSION=$1
+regex='^([0-9]+\.){0,2}(\*|[0-9]+)$'
+if [[ $VERSION =~ $regex ]]; then
+ echo "Version accepted: $VERSION"
+else
+ echo "Version '$VERSION' is not acceptable. Aborting..."
+ exit 1
+fi
+
+PLATFORM=$2
+# TODO refactor this to use something like PLATFORMS.contains(PLATFORM)
+if [[ $PLATFORM == linux ]]; then
+ echo "OS: $PLATFORM"
+elif [[ $PLATFORM == windows ]]; then
+ echo "OS: $PLATFORM"
+elif [[ $PLATFORM == macos ]]; then
+ echo "OS: $PLATFORM"
+else
+ echo "Platform '$PLATFORM' is not acceptable. Aborting..."
+ exit 1
+fi
+
+#if [[ ! -e .env ]]; then
+# echo "File .env not found. Aborting..."
+# exit 1
+#fi
+#echo "Sourcing .env file..."
+#source .env
+
+echo "calling the compile-executable.sh..."
+./compile-executable.sh $1 $2 $3 $4
+
+echo "calling the create-installer.sh..."
+./create-installer.sh $1 $2 $3 $4
+
+echo "calling the deployToRepo.sh..."
+./deployToRepo.sh $1 $2 $3 $4
+
+cd ${OLD_PWD}
diff --git a/installer/compile-executable.sh b/installer/compile-executable.sh
new file mode 100755
index 0000000..cf1160c
--- /dev/null
+++ b/installer/compile-executable.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+echo amount of arguments: $#
+
+if [[ $# -ne 4 ]]; then
+ echo "Build script has to be called with exactly two arguments."
+ echo "The version number (Major.Minor.Patch) as its first argument."
+ echo "... and the OS (linux, windows or macos) as the second argument"
+ echo "TODO: DOCUMENT ARGUMENTS 3 & 4!!!"
+ echo "Aborting..."
+ exit 1
+fi
+
+VERSION=$1
+regex='^([0-9]+\.){0,2}(\*|[0-9]+)$'
+if [[ $VERSION =~ $regex ]]; then
+ echo "Version accepted: $VERSION"
+else
+ echo "Version '$VERSION' is not acceptable. Aborting..."
+ exit 1
+fi
+
+PLATFORM=$2
+# TODO refactor this to use something like PLATFORMS.contains(PLATFORM)
+if [[ $PLATFORM == linux ]]; then
+ echo "OS: $PLATFORM"
+ #QMAKE_EXE="qmake"
+ COMPILER_EXE="make"
+elif [[ $PLATFORM == windows ]]; then
+ echo "OS: $PLATFORM"
+ #QMAKE_EXE="$QMAKE_LOCATION/qmake"
+ #COMPILER_EXE="$MINGWROOT/bin/mingw32-make"
+elif [[ $PLATFORM == macos ]]; then
+ echo "OS: $PLATFORM"
+ #QMAKE_EXE="qmake"
+ COMPILER_EXE="make"
+else
+ echo "Platform '$PLATFORM' is not acceptable. Aborting..."
+ exit 1
+fi
+
+echo "Starting build ..."
+OLD_PWD=$(pwd)
+BUILD_DIR="_build"
+echo "starting in: ${OLD_PWD}"
+
+# if [[ -e $BUILD_DIR/$TARGET ]]; then
+# echo "old build dir has old content, cleaning the build directory..."
+# rm -r $BUILD_DIR/*
+# fi
+cd $BUILD_DIR
+echo "Building in $(pwd) ..."
+
+echo "Building whole project..."
+echo "Running CMake..."
+cmake -DCMAKE_BUILD_TYPE=Release ../../
+
+echo "Compiling..."
+$COMPILER_EXE
diff --git a/installer/config/config.xml.in b/installer/config/config.xml.in
new file mode 100644
index 0000000..b02c894
--- /dev/null
+++ b/installer/config/config.xml.in
@@ -0,0 +1,16 @@
+
+
+ ${PROJECT_NAME}
+ 1.0.0
+ ${PROJECT_NAME} Installer
+ Working-Copy Collective
+ Utilities
+ @ApplicationsDir@/${PROJECT_NAME}
+
+
+ ${CONFIG_REPO_URL}/${PLATFORM}/packages
+ 1
+ The ${PROJECT_NAME} repository
+
+
+
diff --git a/installer/create-installer.sh b/installer/create-installer.sh
new file mode 100755
index 0000000..f8724ad
--- /dev/null
+++ b/installer/create-installer.sh
@@ -0,0 +1,112 @@
+#!/bin/bash
+
+if [[ $# -ne 4 ]]; then
+ echo "Build script has to be called with exactly two arguments."
+ echo "The version number (Major.Minor.Patch) as its first argument."
+ echo "... and the OS (linux, windows or macos) as the second argument"
+ echo "TODO: DOCUMENT ARGUMENTS 3 & 4!!!"
+ echo "Aborting..."
+ exit 1
+fi
+
+VERSION=$1
+regex='^([0-9]+\.){0,2}(\*|[0-9]+)$'
+if [[ $VERSION =~ $regex ]]; then
+ echo "Version accepted: $VERSION"
+else
+ echo "Version '$VERSION' is not acceptable. Aborting..."
+ exit 1
+fi
+
+PLATFORM=$2
+TARGET=$3
+EXECUTABLE="$TARGET-Widgets"
+BUILD_PREFIX="_build/UIs/GenericWidgets"
+# TODO refactor this to use PLATFORMS.contains(PLATFORM)
+if [[ $PLATFORM == linux ]]; then
+ echo "OS: $PLATFORM"
+ BUILD_DIR=$BUILD_PREFIX
+elif [[ $PLATFORM == windows ]]; then
+ echo "OS: $PLATFORM"
+ BUILD_DIR="$BUILD_PREFIX/release"
+elif [[ $PLATFORM == macos ]]; then
+ echo "OS: $PLATFORM"
+ BUILD_DIR=$BUILD_PREFIX
+else
+ echo "Version '$PLATFORM' is not acceptable. Aborting..."
+ exit 1
+fi
+
+#if [[ ! -e .env ]]; then
+# echo "File .env not found. Aborting..."
+# exit 1
+#fi
+#echo "Sourcing .env file..."
+#source .env
+
+PACKAGENAME=$4
+INSTALLER_DIR="_build/installer"
+PKG_DATA_DIR="$INSTALLER_DIR/packages/$PACKAGENAME/data"
+PKG_META_DIR="$INSTALLER_DIR/packages/$PACKAGENAME/meta"
+ASSET_DIR="../assets/icons"
+OUTPUT_DIR="../_output/installer"
+
+if [[ -e $PKG_DATA_DIR/$TARGET ]]; then
+ echo "old $TARGET exe exists, cleaning the package data directory..."
+ rm -r $PKG_DATA_DIR/*
+fi
+
+if [[ ! -d $PKG_DATA_DIR ]]; then
+ echo "creating package data folder..."
+ mkdir -p $PKG_DATA_DIR
+fi
+
+mv $INSTALLER_DIR/packages/meta $PKG_META_DIR
+
+echo "copying executable..."
+if [[ $PLATFORM == macos ]]; then
+ cp -r $BUILD_DIR/$EXECUTABLE.app $PKG_DATA_DIR/$TARGET.app
+else
+ cp $BUILD_DIR/$EXECUTABLE $PKG_DATA_DIR/$TARGET
+fi
+
+echo "copying assets..."
+cp $ASSET_DIR/$TARGET.png $PKG_DATA_DIR/$TARGET.png
+
+echo "copying license..."
+cp ../LICENSE $PKG_META_DIR/license.txt
+
+### Platform dependencies
+echo "Applying platform dependencies..."
+INSTALLER_APPENDIX="installer"
+if [[ $PLATFORM == linux ]]; then
+ echo "Linux..."
+# echo "Patching the config.xml"
+# sed -i "s//$PLATFORM/g" config/config.xml
+elif [[ $PLATFORM == windows ]]; then
+ echo "Windows..."
+ cd $PKG_DATA_DIR
+ echo "Copying OpenSSL dlls..."
+ cp /c/Qt/Tools/OpenSSL/Win_x64/bin/libcrypto-1_1-x64.dll .
+ cp /c/Qt/Tools/OpenSSL/Win_x64/bin/libssl-1_1-x64.dll .
+ echo "Calling windeployqt..."
+ $QMAKE_LOCATION/windeployqt $TARGET.exe
+ cd -
+# echo "Patching the config.xml"
+# sed -i "s//$PLATFORM/g" config/config.xml
+elif [[ $PLATFORM == macos ]]; then
+ echo "MacOS..."
+ echo "Calling macdeployqt..."
+ INSTALLER_APPENDIX="installer.dmg"
+ cd $PKG_DATA_DIR
+ macdeployqt $TARGET.app
+ cd -
+# echo "Patching the config.xml..."
+# sed -i '' "s//$PLATFORM/g" config/config.xml
+fi
+
+
+echo "running binarycreator..."
+#echo "$QIF_LOCATION/binarycreator"
+binarycreator -n -c $INSTALLER_DIR/config/config.xml -p $INSTALLER_DIR/packages $OUTPUT_DIR/$TARGET-$PLATFORM-$VERSION-$INSTALLER_APPENDIX-online
+binarycreator -f -c $INSTALLER_DIR/config/config.xml -p $INSTALLER_DIR/packages $OUTPUT_DIR/$TARGET-$PLATFORM-$VERSION-$INSTALLER_APPENDIX-offline
diff --git a/installer/deployToRepo.sh b/installer/deployToRepo.sh
new file mode 100755
index 0000000..da801fa
--- /dev/null
+++ b/installer/deployToRepo.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+echo amount of arguments: $#
+
+if [[ $# -ne 4 ]]; then
+ echo "Build script has to be called with exactly two arguments."
+ echo "The version number (Major.Minor.Patch) as its first argument."
+ echo "... and the OS (linux, windows or macos) as the second argument"
+ echo "TODO: DOCUMENT ARGUMENTS 3 & 4!!!"
+ echo "Aborting..."
+ exit 1
+fi
+
+VERSION=$1
+regex='^([0-9]+\.){0,2}(\*|[0-9]+)$'
+if [[ $VERSION =~ $regex ]]; then
+ echo "Version accepted: $VERSION"
+else
+ echo "Version '$VERSION' is not acceptable. Aborting..."
+ exit 1
+fi
+
+if [[ ! -e .env ]]; then
+ exit 1
+fi
+echo "Sourcing .env file..."
+source .env
+
+PACKAGENAME=$4
+
+PLATFORM=$2
+
+PACKAGE_DIR="_build/installer/packages"
+OUTPUT_DIR="../_output/repository"
+echo "executing repo-gen..."
+repogen --update -p $PACKAGE_DIR $OUTPUT_DIR
+
+if [[ $PUSH_REPO_URL != "" ]]; then
+ echo "pushing package data to repo..."
+ echo "PUSH_REPO_URL: $PUSH_REPO_URL"
+ scp -r $OUTPUT_DIR/* $PUSH_REPO_URL/$PLATFORM/packages/
+fi
+
+cd ${OLD_PWD}
diff --git a/installer/packages/defaultPackage/meta/installscript.qs.in b/installer/packages/defaultPackage/meta/installscript.qs.in
new file mode 100644
index 0000000..15d5ac1
--- /dev/null
+++ b/installer/packages/defaultPackage/meta/installscript.qs.in
@@ -0,0 +1,24 @@
+function Component() {}
+
+Component.prototype.isDefault = function() {
+ return true;
+}
+
+Component.prototype.createOperations = function() {
+
+ try {
+ component.createOperations();
+ } catch (e) {
+ print(e);
+ }
+
+ if (installer.value("os") === "win")
+ {
+ component.addOperation("CreateShortcut", "@TargetDir@/${PROJECT_NAME}.exe", "@DesktopDir@/${PROJECT_NAME}.lnk");
+ }
+ if (installer.value("os") === "x11")
+ {
+ component.addOperation("CreateDesktopEntry", "/usr/share/applications/${PROJECT_NAME}.desktop", "Version=1.0\nType=Application\nTerminal=false\nCategories=Utility\nExec=@TargetDir@/${PROJECT_NAME}\nName=${PROJECT_NAME}\nIcon=@TargetDir@/${PROJECT_NAME}.png\nName[en_US]=${PROJECT_NAME}");
+ //component.addElevatedOperation("Copy", "/usr/share/applications/${PROJECT_NAME}.desktop", "@HomeDir@/Desktop/${PROJECT_NAME}.desktop");
+ }
+}
diff --git a/installer/packages/defaultPackage/meta/license.txt.in b/installer/packages/defaultPackage/meta/license.txt.in
new file mode 100644
index 0000000..c83e137
--- /dev/null
+++ b/installer/packages/defaultPackage/meta/license.txt.in
@@ -0,0 +1 @@
+The fantastic license, have you heard of the Tea Public License Agreement yet?
diff --git a/installer/packages/defaultPackage/meta/package.xml.in b/installer/packages/defaultPackage/meta/package.xml.in
new file mode 100644
index 0000000..6be9b59
--- /dev/null
+++ b/installer/packages/defaultPackage/meta/package.xml.in
@@ -0,0 +1,13 @@
+
+
+ The application itself
+ Installs the ${PROJECT_NAME} executable.
+ ${PROJECT_VERSION}
+ ${CURRENT_DATE}
+
+
+
+ true
+ script
+
+
diff --git a/libs/3rdParty/rapidcsv b/libs/3rdParty/rapidcsv
new file mode 160000
index 0000000..03133b5
--- /dev/null
+++ b/libs/3rdParty/rapidcsv
@@ -0,0 +1 @@
+Subproject commit 03133b59e17ca92ecf886b611d87a03ad2bb2a8f
diff --git a/libs/GenericCore b/libs/GenericCore
new file mode 160000
index 0000000..c15e542
--- /dev/null
+++ b/libs/GenericCore
@@ -0,0 +1 @@
+Subproject commit c15e5425a7721c144b7f108be4511ed74a3053f1
diff --git a/tests/GenericCoreTests/CMakeLists.txt b/tests/GenericCoreTests/CMakeLists.txt
new file mode 100644
index 0000000..21d44fe
--- /dev/null
+++ b/tests/GenericCoreTests/CMakeLists.txt
@@ -0,0 +1,30 @@
+include(FetchContent)
+
+set(TARGET_APP "core_test")
+
+FetchContent_Declare(
+ googletest
+ GIT_REPOSITORY https://github.com/google/googletest.git
+ GIT_TAG v1.17.0
+)
+FetchContent_MakeAvailable(googletest)
+add_library(GTest::GTest INTERFACE IMPORTED)
+target_link_libraries(GTest::GTest INTERFACE gtest_main)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core LinguistTools)
+find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core LinguistTools)
+
+add_executable(${TARGET_APP} core_test.cpp)
+
+target_include_directories(${TARGET_APP} PRIVATE ${CORE_LIB_DIR}/include)
+
+target_link_libraries(${TARGET_APP}
+ PRIVATE
+ GTest::GTest
+ GenericCore)
+target_link_libraries(${TARGET_APP} PUBLIC Qt${QT_VERSION_MAJOR}::Core)
+
+add_test(core_gtests ${TARGET_APP})
diff --git a/tests/GenericCoreTests/core_test.cpp b/tests/GenericCoreTests/core_test.cpp
new file mode 100644
index 0000000..ecde362
--- /dev/null
+++ b/tests/GenericCoreTests/core_test.cpp
@@ -0,0 +1,26 @@
+#include
+
+#include
+
+#include "../../libs/GenericCore/genericcore.h"
+
+QT_BEGIN_NAMESPACE
+inline void PrintTo(const QString& qString, ::std::ostream* os) { *os << qUtf8Printable(qString); }
+QT_END_NAMESPACE
+
+TEST(CoreTests, TestEqualString) {
+ const QString coreName("GenericCore");
+ const QString coreVersion("0.2.0");
+ const auto expected = QString("%1 (Version %2)").arg(coreName).arg(coreVersion);
+ auto core = std::make_unique();
+ const auto actual = core->toString();
+ // const auto actual = multiply(1, 1);
+ ASSERT_EQ(expected, actual);
+}
+
+TEST(CoreTests, TestNotEqualString) {
+ const QString expected = QString("Hello from the Core!");
+ auto core = std::make_unique();
+ const QString actual = core->toString();
+ ASSERT_NE(expected, actual);
+}
diff --git a/tests/additional files for testing/CSV import and export/CSV-Test-missing-column.csv b/tests/additional files for testing/CSV import and export/CSV-Test-missing-column.csv
new file mode 100644
index 0000000..98eadc6
--- /dev/null
+++ b/tests/additional files for testing/CSV import and export/CSV-Test-missing-column.csv
@@ -0,0 +1,8 @@
+Name,Description,Info,Factor
+Item 0,This is item 0,Info of item 0,0
+Item 1,This is item 1,Info of item 1,1
+Item 2,This is item 2,Info of item 2,2
+Item 3,This is item 3,Info of item 3,3
+Item 4,This is item 4,Info of item 4,4
+Item 5,This is item 5,Info of item 5,5
+Item 6,This is item 6,Info of item 6,6
diff --git a/tests/additional files for testing/CSV import and export/CSV-Test-success.csv b/tests/additional files for testing/CSV import and export/CSV-Test-success.csv
new file mode 100644
index 0000000..ef770f9
--- /dev/null
+++ b/tests/additional files for testing/CSV import and export/CSV-Test-success.csv
@@ -0,0 +1,8 @@
+Name,Description,Info,Amount,Factor
+Item 0,This is item 0,Info of item 0,0,0
+Item 1,This is item 1,Info of item 1,1,1
+Item 2,This is item 2,Info of item 2,2,2
+Item 3,This is item 3,Info of item 3,3,3
+Item 4,This is item 4,Info of item 4,4,4
+Item 5,This is item 5,Info of item 5,5,5
+Item 6,This is item 6,Info of item 6,6,6