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 +![Tests](https://github.com/masonlet/tasktracker/actions/workflows/test.yml/badge.svg) +[![C++17](https://img.shields.io/badge/C%2B%2B-17-blue.svg)](https://isocpp.org/std/the-standard) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./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. ![Example](./images/example.png) +
+ ## 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