diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..c02df12
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,19 @@
+name: Tests
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+
+jobs:
+ test:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Configure CMake
+ run: cmake -B build -DBUILD_TESTS=ON
+ - name: Build
+ run: cmake --build build --config Release
+ - name: Run tests
+ run: ctest --test-dir build/shared -C Release --output-on-failure
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8e482d1..57ee845 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10)
-project(tasktracker_installer)
+project(tasktracker_installer VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 17)
add_subdirectory(shared)
diff --git a/README.md b/README.md
index ec5ec20..7a21b3c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,7 @@
# Tasktracker
+
+[](https://isocpp.org/std/the-standard)
+[](./LICENSE)
Lightweight Windows 10/11 tool to manage folder statuses from the right-click context menu.
Quickly mark folders as Finished, Hidden, Unfinished, or reset to Default.
@@ -23,6 +26,8 @@ Right-click any folder to see Tasktracker options.
Folder icons reflect their current status.

+
+
## Prerequisites
- Windows 10 or 11
- Administrator privileges (required for context menu installation)
@@ -32,27 +37,33 @@ Folder icons reflect their current status.
2. Run the installer
3. To uninstall, simply re-run the installer
+
+
## Building the Project
-To build TaskTracker and Installer from source using CMake:
+### Prerequisites
+- C++17
+- CMake 3.10+
-### 1. Clone the Repository
+### Build Steps
```bash
+# 1. Clone the repository
git clone https://github.com/masonlet/tasktracker.git
cd tasktracker
-```
-### 2. Create a Build Directory and Generate Build Files
-```bash
-mkdir build
-cd build
-cmake ..
+# 2. Create a build directory and generate build files
+cmake -B build
+
+# 3. Build the project
+cmake --build build
```
+Or open the generated `.sln` file in Visual Studio and build the solution.
-### 3. Build the Project
+### Running Tests
```bash
-cmake --build .
+cmake -B build -DBUILD_TESTS=ON
+cmake --build build
+ctest --test-dir build/shared
```
-Or open the generated `.sln` file in Visual Studio and build the solution.
## License
MIT License — see [LICENSE](./LICENSE) for details.
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index 0b84652..9a3bf02 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -14,5 +14,28 @@ source_group(TREE ${SHARED_INC_DIR} PREFIX "Header Files" FILES ${SHARED_INC})
source_group(TREE ${SHARED_SRC_DIR} PREFIX "Source Files" FILES ${SHARED_SRC})
add_library(shared STATIC ${SHARED_INC} ${SHARED_SRC})
-
target_include_directories(shared PUBLIC ${SHARED_INC_DIR})
+
+option(BUILD_TESTS "Build unit tests" OFF)
+
+if(BUILD_TESTS)
+ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
+
+ enable_testing()
+ include(FetchContent)
+ FetchContent_Declare(
+ googletest
+ GIT_REPOSITORY https://github.com/google/googletest.git
+ GIT_TAG v1.14.0
+ )
+
+ # For Windows: Prevent overriding the parent project's compiler/linker settings
+ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
+ FetchContent_MakeAvailable(googletest)
+
+ set_target_properties(gtest gtest_main gmock gmock_main PROPERTIES
+ FOLDER "Tests/GoogleTest"
+ )
+
+ add_subdirectory(tests)
+endif()
diff --git a/shared/tests/CMakeLists.txt b/shared/tests/CMakeLists.txt
new file mode 100644
index 0000000..430ba36
--- /dev/null
+++ b/shared/tests/CMakeLists.txt
@@ -0,0 +1,16 @@
+add_executable(shared_tests
+ shared_test.cpp
+)
+
+target_link_libraries(shared_tests
+ PRIVATE
+ shared
+ GTest::gtest_main
+)
+
+set_target_properties(shared_tests PROPERTIES
+ FOLDER "Tests"
+)
+
+include(GoogleTest)
+gtest_discover_tests(shared_tests)
diff --git a/shared/tests/shared_test.cpp b/shared/tests/shared_test.cpp
new file mode 100644
index 0000000..6c7f77e
--- /dev/null
+++ b/shared/tests/shared_test.cpp
@@ -0,0 +1,67 @@
+#include
+#include
+#include
+#include "../inc/file_utils.hpp"
+#include "../inc/system_utils.hpp"
+
+using TPath = TaskTracker::Path;
+namespace {
+ TPath makeTempPath(const std::wstring& name) {
+ return std::filesystem::temp_directory_path() / name;
+ }
+}
+
+namespace TFile = TaskTracker::FileUtils;
+TEST(FileUtils, FileExists_ReturnsTrueWhenPresent) {
+ TPath path = makeTempPath(L"test_exist.txt");
+ std::ofstream{ path };
+
+ EXPECT_TRUE(TFile::fileExists(path, L"test", false));
+ std::filesystem::remove(path);
+}
+
+TEST(FileUtils, DeleteFile_RemovesExistingFile) {
+ TPath path = makeTempPath(L"test_delete.txt");
+ std::ofstream{ path };
+
+ EXPECT_TRUE(TFile::deleteFile(path, L"test"));
+ EXPECT_FALSE(std::filesystem::exists(path));
+}
+
+TEST(FileUtils, CreateDirectory_CreatesNewDirectory) {
+ TPath path = makeTempPath(L"test_create_dir");
+ EXPECT_TRUE(TFile::createDirectory(path, L"test"));
+ EXPECT_TRUE(std::filesystem::is_directory(path));
+ std::filesystem::remove(path);
+}
+
+TEST(FileUtils, DeleteDirectory_RemovesExistingDirectory) {
+ TPath path = makeTempPath(L"test_delete_dir");
+ std::filesystem::create_directory(path);
+ EXPECT_TRUE(TFile::deleteDirectory(path, L"test"));
+ EXPECT_FALSE(std::filesystem::exists(path));
+}
+
+namespace TSystem = TaskTracker::SystemUtils;
+TEST(SystemUtils, GetProgramFilesPath_ReturnsValidPath) {
+ TPath path = TSystem::getProgramFilesPath();
+ EXPECT_FALSE(path.empty());
+ EXPECT_TRUE(std::filesystem::exists(path));
+}
+
+TEST(SystemUtils, IsValidPath_RejectsNonexistent) {
+ TPath fakePath = makeTempPath(L"nonexistent_dir");
+ EXPECT_FALSE(TSystem::isValidPath(fakePath, L"test"));
+}
+
+TEST(SystemUtils, IsValidPath_RejectsExistingFile) {
+ TPath filePath = makeTempPath(L"not_a_dir.txt");
+ std::ofstream{ filePath };
+ EXPECT_FALSE(TSystem::isValidPath(filePath, L"test"));
+ std::filesystem::remove(filePath);
+}
+
+TEST(SystemUtils, DeleteDesktopIni_HandlesNonexistentFile) {
+ TPath path = makeTempPath(L"desktop.ini");
+ EXPECT_TRUE(TSystem::deleteDesktopIni(path, L"test"));
+}
\ No newline at end of file