diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6847108..b812c42 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: danger89/gtk3-docker-cmake-ninja:2.5 +image: danger89/gtk3-docker-cmake-ninja:3.0 stages: - build #- upload @@ -41,10 +41,9 @@ code_style_guidelines: stage: build script: "./scripts/check-format.sh" -# TODO: Testing! -#unit_test: -# stage: test -# script : +unit_test: + stage: test + script: "./scripts/build-run-tests.sh" sast: stage: test diff --git a/.vscode/cmake-variants.json b/.vscode/cmake-variants.json new file mode 100644 index 0000000..a08f520 --- /dev/null +++ b/.vscode/cmake-variants.json @@ -0,0 +1,40 @@ +{ + "buildType": { + "default": "debug", + "choices": { + "debug": { + "short": "Debug", + "long": "Emit debug information without performing optimizations", + "buildType": "Debug", + "settings": { + "UNITTEST": false + } + }, + "release": { + "short": "Release", + "long": "Enable optimizations, omit debug info", + "buildType": "Release", + "settings": { + "UNITTEST": false + } + }, + "reldeb": { + "short": "RelWithDebInfo", + "long": "Perform optimizations AND include debugging information", + "buildType": "RelWithDebInfo", + "settings": { + "UNITTEST": false + } + }, + "test": { + "short": "UnitTests", + "long": "Build + run unit tests", + "buildType": "Debug", + "settings": { + "UNITTEST": true, + "DOXYGEN": false + } + } + } + } +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 521c887..a8e1df2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -80,6 +80,7 @@ "node.h": "c", "*.in": "cpp", "superscript.h": "c", - "cmark-gfm.h": "c" + "cmark-gfm.h": "c", + "any": "cpp" } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index aabe9af..48bb364 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required (VERSION 3.16.0) option(DOXYGEN "Build Doxygen documentation" ON) +option(UNITTEST "Build unit tests") list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") @@ -13,6 +14,9 @@ project(libreweb-browser message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") message(STATUS "PROJECT_VERSION: ${PROJECT_VERSION}") +if(UNITTEST) + message(STATUS "Building the unit tests") +endif() # Build docs using Doxygen if(DOXYGEN) @@ -38,7 +42,7 @@ if(WIN32) set(WINDOWS_FLAGS -mwindows) endif() -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -pedantic -Werror=incompatible-pointer-types") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 -DNDEBUG") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror") @@ -55,6 +59,10 @@ add_subdirectory (lib/ipfs-http-client) add_subdirectory (lib/whereami) add_subdirectory (src) +if(UNITTEST) + add_subdirectory(tst) +endif() + # Additional install files if(WIN32) # Windows specific diff --git a/README.md b/README.md index e7bc848..3259029 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,13 @@ For the GNU/Linux build you need at least: * Libcurl (Package: `libcurl4-openssl-dev`) * GTK & Pango (including C++ bindings): * Package: `libgtkmm-3.0-dev` under Debian based distros + +Depedencies for tests: + +* X virtual framebuffer (Package: `xvfb`) * Clang-format (Package: `clang-format`) -*Note:* For cross-compiling towards Windows, see the cross-compile section below. +*Note:* For cross-compiling towards Windows, see the cross-compile section down below. ### Build @@ -99,9 +103,23 @@ Start the Linux build, which is using CMake and Ninja build system, using the wr Optionally, use the VSCode `CMake Tools` extension to start the build or build with debug targets. -Build a release target, including packaging under GNU/Linux, using: `./scripts/build-lnx-prod.sh` +#### Linux Packaging -*Note:* Root access is required for Linux packaging; add `/opt/mxe/usr/bin` to the secure_path using: `sudo visudo`. +*Note:* (Linux) Packages are already [available under releases](https://gitlab.melroy.org/libreweb/browser/-/releases). + +To build a release target yourself including packaging under GNU/Linux, use: `./scripts/build-lnx-prod.sh` + +Root access is required when building Linux packages; add `/opt/mxe/usr/bin` to the secure_path using: `sudo visudo`. + +### Unit testing + +To execute the **unit tests** you can configure with `cmake -DUNITTEST:BOOL=TRUE` and build. Execute: `ctest` command in the `tst` target directory. + +Or just use script: + +```sh +./scripts/build-run-tests.sh +``` ### C++ Coding Style Guidelines diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh index 20bb8ca..4d4343f 100755 --- a/scripts/build-docs.sh +++ b/scripts/build-docs.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # By: Melroy van den Berg -# Descriptiopn: Only build the documentation (used in CI/CD) +# Description: Only build the documentation (used in CI/CD) mkdir build_docs cd build_docs diff --git a/scripts/build-lnx-no-docs.sh b/scripts/build-lnx-no-docs.sh new file mode 100755 index 0000000..2eaa509 --- /dev/null +++ b/scripts/build-lnx-no-docs.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# By: Melroy van den Berg +# Description: Development build for Linux, same as build-lnx.sh but without Doxygen + +if [ ! -d "build" ]; then + echo "Creating build directory..." + mkdir build +fi + +if [ -z "$(ls build)" ]; then + echo "INFO: Run cmake & ninja" + cd build + cmake -G Ninja -DDOXYGEN:BOOL=FALSE .. +else + echo "INFO: Only run ninja..." + cd build +fi +ninja diff --git a/scripts/build-run-tests.sh b/scripts/build-run-tests.sh new file mode 100755 index 0000000..5a6a538 --- /dev/null +++ b/scripts/build-run-tests.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# By: Melroy van den Berg +# Description: Build & run unit-tests + +if [ ! -d "build_test" ]; then + echo "Creating test build directory..." + mkdir build_test +fi + +if [ -z "$(ls build_test)" ]; then + echo "INFO: Run cmake & ninja" + cd build_test + cmake -G Ninja -DDOXYGEN:BOOL=FALSE -DUNITTEST:BOOL=TRUE .. +else + echo "INFO: Only run ninja..." + cd build_test +fi +# Build & run unit tests +ninja tests diff --git a/scripts/check-format.sh b/scripts/check-format.sh index 7e6bafc..d7e5c47 100755 --- a/scripts/check-format.sh +++ b/scripts/check-format.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash # Description: Check the coding style guidelines (only dry-run) -find src/ -iname *.h -o -iname *.cc -o -iname *.h.in | xargs clang-format --dry-run -Werror -style=file -fallback-style=LLVM +find src/ tst/ -iname *.h -o -iname *.cc -o -iname *.h.in | xargs clang-format --dry-run -Werror -style=file -fallback-style=LLVM diff --git a/scripts/cpp-check.sh b/scripts/cpp-check.sh index fc3494c..732cab7 100755 --- a/scripts/cpp-check.sh +++ b/scripts/cpp-check.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash # TODO: add to following flag to the cppcheck: --addon=cert -cppcheck --enable=all --suppressions-list=suppressions.txt --error-exitcode=1 "$@" -I lib/commonmarker/src/ -I lib/commonmarker/extensions/ ./src +cppcheck --enable=all --library=googletest --suppressions-list=suppressions.txt --error-exitcode=1 "$@" -I lib/commonmarker/src/ -I lib/commonmarker/extensions/ ./src ./tst diff --git a/scripts/fix-format.sh b/scripts/fix-format.sh index b82df8c..ea39213 100755 --- a/scripts/fix-format.sh +++ b/scripts/fix-format.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash # Description: Check the coding style guidelines & fix them automatically -find src/ -iname *.h -o -iname *.cc -o -iname *.h.in | xargs clang-format -i -style=file -fallback-style=LLVM -assume-filename=../.clang-format +find src/ tst/ -iname *.h -o -iname *.cc -o -iname *.h.in | xargs clang-format -i -style=file -fallback-style=LLVM -assume-filename=../.clang-format diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 21cb67f..87e81c5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,7 @@ include(${CMAKE_SOURCE_DIR}/cmake/GSettings.cmake) set(PROJECT_TARGET libreweb-browser) +set(PROJECT_TARGET_LIB ${PROJECT_TARGET}-lib) set(THREADS_PREFER_PTHREAD_FLAG ON) # Find required dependencies @@ -11,7 +12,7 @@ pkg_check_modules(GTKMM REQUIRED gtkmm-3.0) # Generate Project version header file configure_file(${CMAKE_CURRENT_SOURCE_DIR}/project_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/project_config.h) -# In order to find project_config.h +# Include the binary dir, in order to find project_config.h set(CMAKE_INCLUDE_CURRENT_DIR ON) # Source code @@ -20,6 +21,7 @@ set(HEADERS draw.h file.h ipfs.h + middleware-i.h middleware.h toolbar-button.h mainwindow.h @@ -76,52 +78,83 @@ if(WIN32) set_source_files_properties(${WINDOWS_RES} PROPERTIES LANGUAGE RC) endif() -add_executable(${PROJECT_TARGET} ${GSCHEMA_RING} ${WINDOWS_RES} ${SOURCES}) - -## Definitions just in case -# Define _WIN32 for Windows platforms -if(WIN32) - target_compile_definitions(${PROJECT_TARGET} PRIVATE _WIN32) -endif() -# Define __linux__ for Unix platforms -if (UNIX) - target_compile_definitions(${PROJECT_TARGET} PRIVATE __linux__) -endif() - -# Add fallback for std filesystem in older GCC versions -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.4) - message(FATAL_ERROR "You are on an extremely old version of GCC. Please update your compiler to at least GCC 8.0, preferably latest") - elseif (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) - message(WARNING "Old Version of GCC detected. Using Legacy C++ support") - # Add stdc++fs library in older GCC compiler versions - set(CXX_FILESYSTEM_LIBRARIES "stdc++fs") - target_compile_definitions(${PROJECT_TARGET} PUBLIC LEGACY_CXX) - endif() -endif() - # Get include list the cmark binary directory for the generated config.h, .._version.h & .._export.h files # Get include list the cmark extensions binary directory for the generated ..._export.h file -get_property(CMAKE_BINARY_DIR GLOBAL PROPERTY COMMONMARKER_BINARY_DIR) -get_property(CMAKE_EXTENSIONS_BINARY_DIR GLOBAL PROPERTY COMMONMARKER_EXTENSIONS_BINARY_DIR) +get_property(CMARK_BINARY_DIR GLOBAL PROPERTY COMMONMARKER_BINARY_DIR) +get_property(CMARK_EXTENSIONS_BINARY_DIR GLOBAL PROPERTY COMMONMARKER_EXTENSIONS_BINARY_DIR) -target_include_directories(${PROJECT_TARGET} PRIVATE - ${CMAKE_BINARY_DIR} - ${CMAKE_EXTENSIONS_BINARY_DIR} - ${GTKMM_INCLUDE_DIRS} -) -target_link_directories(${PROJECT_TARGET} PRIVATE ${GTKMM_LIBRARY_DIRS}) -target_link_libraries(${PROJECT_TARGET} PRIVATE - LibCommonMarker - LibCommonMarkerExtensions - ipfs-http-client - whereami - Threads::Threads - ${CXX_FILESYSTEM_LIBRARIES} - ${GTKMM_LIBRARIES} - nlohmann_json::nlohmann_json -) -target_compile_options(${PROJECT_TARGET} PRIVATE ${GTKMM_CFLAGS_OTHER}) +if(NOT UNITTEST) + # LibreWeb binary + add_executable(${PROJECT_TARGET} ${GSCHEMA_RING} ${WINDOWS_RES} ${SOURCES}) -# Install browser binary -install(TARGETS ${PROJECT_TARGET} RUNTIME DESTINATION bin) + ## Definitions just in case + # Define _WIN32 for Windows platforms + if(WIN32) + target_compile_definitions(${PROJECT_TARGET} PRIVATE _WIN32) + endif() + # Define __linux__ for Unix platforms + if (UNIX) + target_compile_definitions(${PROJECT_TARGET} PRIVATE __linux__) + endif() + + # Add fallback for std filesystem in older GCC versions + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.4) + message(FATAL_ERROR "You are on an extremely old version of GCC. Please update your compiler to at least GCC 8.0, preferably latest") + elseif (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) + message(WARNING "Old Version of GCC detected. Using Legacy C++ support") + # Add stdc++fs library in older GCC compiler versions + set(CXX_FILESYSTEM_LIBRARIES "stdc++fs") + target_compile_definitions(${PROJECT_TARGET} PUBLIC LEGACY_CXX) + endif() + endif() + + target_include_directories(${PROJECT_TARGET} PRIVATE + ${CMARK_BINARY_DIR} + ${CMARK_EXTENSIONS_BINARY_DIR} + ${GTKMM_INCLUDE_DIRS} + ) + target_link_directories(${PROJECT_TARGET} PRIVATE ${GTKMM_LIBRARY_DIRS}) + target_link_libraries(${PROJECT_TARGET} PRIVATE + LibCommonMarker + LibCommonMarkerExtensions + ipfs-http-client + whereami + Threads::Threads + ${CXX_FILESYSTEM_LIBRARIES} + ${GTKMM_LIBRARIES} + nlohmann_json::nlohmann_json + ) + target_compile_options(${PROJECT_TARGET} PRIVATE ${GTKMM_CFLAGS_OTHER}) + + # Install browser binary + install(TARGETS ${PROJECT_TARGET} RUNTIME DESTINATION bin) +else() + # Build libraries for unit testing + add_library(${PROJECT_TARGET_LIB}-file STATIC file.h file.cc) + add_library(${PROJECT_TARGET_LIB}-draw STATIC draw.h draw.cc md-parser.h md-parser.cc) + add_library(${PROJECT_TARGET_LIB}-parser STATIC md-parser.h md-parser.cc) + + # Only link/include external libs we really need for the unittest libaries + target_include_directories(${PROJECT_TARGET_LIB}-draw PRIVATE + ${CMARK_BINARY_DIR} + ${CMARK_EXTENSIONS_BINARY_DIR} + ${GTKMM_INCLUDE_DIRS} + ) + target_link_libraries(${PROJECT_TARGET_LIB}-draw PRIVATE + LibCommonMarker + LibCommonMarkerExtensions + ${GTKMM_LIBRARIES} + ) + target_compile_options(${PROJECT_TARGET_LIB}-draw PRIVATE ${GTKMM_CFLAGS_OTHER}) + target_include_directories(${PROJECT_TARGET_LIB}-parser PRIVATE + ${CMARK_BINARY_DIR} + ${CMARK_EXTENSIONS_BINARY_DIR} + ${GTKMM_INCLUDE_DIRS} + ) + target_link_directories(${PROJECT_TARGET_LIB}-parser PRIVATE ${GTKMM_LIBRARY_DIRS}) + target_link_libraries(${PROJECT_TARGET_LIB}-parser PRIVATE + LibCommonMarker + LibCommonMarkerExtensions + ) +endif() \ No newline at end of file diff --git a/src/draw.cc b/src/draw.cc index 1854db0..990c65f 100644 --- a/src/draw.cc +++ b/src/draw.cc @@ -1,7 +1,6 @@ #include "draw.h" -#include "middleware.h" +#include "middleware-i.h" #include "node.h" -#include "strikethrough.h" #include "syntax_extension.h" #include #include @@ -11,7 +10,7 @@ #include #include -Draw::Draw(Middleware& middleware) +Draw::Draw(MiddlewareInterface& middleware) : middleware(middleware), buffer(Glib::unwrap(this->get_buffer())), addViewSourceMenuItem(true), @@ -351,7 +350,7 @@ void Draw::newDocument() */ Glib::ustring Draw::getText() const { - return get_buffer().get()->get_text(); + return get_buffer()->get_text(); } /** diff --git a/src/draw.h b/src/draw.h index 70c8514..b333309 100644 --- a/src/draw.h +++ b/src/draw.h @@ -8,7 +8,7 @@ #include #include -class Middleware; +class MiddlewareInterface; /** * \struct UndoRedoData @@ -37,7 +37,7 @@ public: CODE_TYPE_CODE_BLOCK }; - explicit Draw(Middleware& middleware); + explicit Draw(MiddlewareInterface& middleware); void setMessage(const Glib::ustring& message, const Glib::ustring& details = ""); void showStartPage(); void setDocument(cmark_node* rootNode); @@ -81,7 +81,7 @@ protected: void populate_popup(Gtk::Menu* menu); private: - Middleware& middleware; + MiddlewareInterface& middleware; GtkTextBuffer* buffer; bool addViewSourceMenuItem; int headingLevel; diff --git a/src/file.cc b/src/file.cc index 48032ec..9a8a139 100644 --- a/src/file.cc +++ b/src/file.cc @@ -1,6 +1,5 @@ #include "file.h" #include -#include #include #ifdef LEGACY_CXX diff --git a/src/ipfs.cc b/src/ipfs.cc index ddba7a2..217076e 100644 --- a/src/ipfs.cc +++ b/src/ipfs.cc @@ -15,7 +15,7 @@ IPFS::IPFS(const std::string& host, int port, const std::string& timeout) } /** - * \brief Get the number of IPFS peers. Does not throw errors. + * \brief Get the number of IPFS peers. * \return number of peers as size_t */ std::size_t IPFS::getNrPeers() @@ -26,7 +26,7 @@ std::size_t IPFS::getNrPeers() } /** - * \brief Retrieve your IPFS client ID. Does not throw errors. + * \brief Retrieve your IPFS client ID. * \return ID as string */ std::string IPFS::getClientID() @@ -37,7 +37,7 @@ std::string IPFS::getClientID() } /** - * \brief Retrieve your IPFS Public Key. Does not throw errors. + * \brief Retrieve your IPFS Public Key. * \return Public key string */ std::string IPFS::getClientPublicKey() @@ -48,7 +48,7 @@ std::string IPFS::getClientPublicKey() } /** - * \brief Retrieve the Go IPFS daemon version. Does not throw errors. + * \brief Retrieve the Go IPFS daemon version. * \return Version string */ std::string IPFS::getVersion() @@ -59,7 +59,7 @@ std::string IPFS::getVersion() } /** - * \brief Get the number of IPFS peers. Does not throw errors. + * \brief Get the number of IPFS peers. * \return Map with bandwidth information (with keys: 'in' and 'out') */ std::map IPFS::getBandwidthRates() @@ -75,7 +75,7 @@ std::map IPFS::getBandwidthRates() } /** - * \brief Get the stats of the current Repo. Does not throw errors. + * \brief Get the stats of the current Repo. * \return Map with repo stats (with keys: 'repo-size' and 'path') */ std::map> IPFS::getRepoStats() diff --git a/src/middleware-i.h b/src/middleware-i.h new file mode 100644 index 0000000..06f07f7 --- /dev/null +++ b/src/middleware-i.h @@ -0,0 +1,41 @@ +#ifndef MIDDLEWARE_INTERFACE_H +#define MIDDLEWARE_INTERFACE_H + +#include +#include + +/* Forward declarations */ +struct cmark_node; + +/** + * \class MiddlewareInterface + * \brief Pure Middleware interface + */ +class MiddlewareInterface +{ +public: + virtual ~MiddlewareInterface() + { + } + virtual void doRequest(const std::string& path = std::string(), + bool isSetAddressBar = true, + bool isHistoryRequest = false, + bool isDisableEditor = true, + bool isParseContent = true) = 0; + virtual std::string doAdd(const std::string& path) = 0; + virtual void doWrite(const std::string& path, bool isSetAddressAndTitle = true) = 0; + virtual void setContent(const Glib::ustring& content) = 0; + virtual Glib::ustring getContent() const = 0; + virtual cmark_node* parseContent() const = 0; + virtual void resetContentAndPath() = 0; + virtual std::size_t getIPFSNumberOfPeers() const = 0; + virtual int getIPFSRepoSize() const = 0; + virtual std::string getIPFSRepoPath() const = 0; + virtual std::string getIPFSIncomingRate() const = 0; + virtual std::string getIPFSOutcomingRate() const = 0; + virtual std::string getIPFSVersion() const = 0; + virtual std::string getIPFSClientId() const = 0; + virtual std::string getIPFSClientPublicKey() const = 0; +}; + +#endif \ No newline at end of file diff --git a/src/middleware.cc b/src/middleware.cc index 6f34cf4..5d833da 100644 --- a/src/middleware.cc +++ b/src/middleware.cc @@ -130,7 +130,8 @@ Glib::ustring Middleware::getContent() const } /** - * \brief Current content parser middleware + * \brief Current content parser middleware. + * Note: Do not forget to free the document: cmark_node_free(root_node; * \return AST structure (of type cmark_node) */ cmark_node* Middleware::parseContent() const diff --git a/src/middleware.h b/src/middleware.h index 0267d03..bd58f78 100644 --- a/src/middleware.h +++ b/src/middleware.h @@ -2,15 +2,14 @@ #define MIDDLEWARE_H #include "ipfs.h" +#include "middleware-i.h" #include #include #include -#include #include #include #include #include -#include /* Forward declarations */ struct cmark_node; @@ -20,7 +19,7 @@ class MainWindow; * \class Middleware * \brief Handles (IPFS) network requests and File IO from disk towards the GUI */ -class Middleware +class Middleware : public MiddlewareInterface { public: explicit Middleware(MainWindow& mainWindow, const std::string& timeout); @@ -29,21 +28,21 @@ public: bool isSetAddressBar = true, bool isHistoryRequest = false, bool isDisableEditor = true, - bool isParseContent = true); - std::string doAdd(const std::string& path); - void doWrite(const std::string& path, bool isSetAddressAndTitle = true); - void setContent(const Glib::ustring& content); - Glib::ustring getContent() const; - cmark_node* parseContent() const; - void resetContentAndPath(); - std::size_t getIPFSNumberOfPeers() const; - int getIPFSRepoSize() const; - std::string getIPFSRepoPath() const; - std::string getIPFSIncomingRate() const; - std::string getIPFSOutcomingRate() const; - std::string getIPFSVersion() const; - std::string getIPFSClientId() const; - std::string getIPFSClientPublicKey() const; + bool isParseContent = true) override; + std::string doAdd(const std::string& path) override; + void doWrite(const std::string& path, bool isSetAddressAndTitle = true) override; + void setContent(const Glib::ustring& content) override; + Glib::ustring getContent() const override; + cmark_node* parseContent() const override; + void resetContentAndPath() override; + std::size_t getIPFSNumberOfPeers() const override; + int getIPFSRepoSize() const override; + std::string getIPFSRepoPath() const override; + std::string getIPFSIncomingRate() const override; + std::string getIPFSOutcomingRate() const override; + std::string getIPFSVersion() const override; + std::string getIPFSClientId() const override; + std::string getIPFSClientPublicKey() const override; private: MainWindow& mainWindow; diff --git a/tst/CMakeLists.txt b/tst/CMakeLists.txt new file mode 100644 index 0000000..cd56fc1 --- /dev/null +++ b/tst/CMakeLists.txt @@ -0,0 +1,41 @@ + +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTKMM REQUIRED gtkmm-3.0) + +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/e2239ee6043f73722e7aa812a459f54a28552929.zip +) +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +get_property(CMARK_BINARY_DIR GLOBAL PROPERTY COMMONMARKER_BINARY_DIR) + +enable_testing() + +add_executable(draw draw_test.cc mock-middleware.h) +target_include_directories(draw PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMARK_BINARY_DIR} ${GTKMM_INCLUDE_DIRS}) +target_link_libraries(draw PRIVATE libreweb-browser-lib-draw ${GTKMM_LIBRARIES} LibCommonMarker gtest_main gmock_main) +add_test(NAME draw_test COMMAND draw) + +add_executable(file file_test.cc) +target_include_directories(file PRIVATE ${CMAKE_SOURCE_DIR}/src) +target_link_libraries(file PRIVATE libreweb-browser-lib-file gtest_main) +add_test(NAME file_test COMMAND file) + +add_executable(parser parser_test.cc) +target_include_directories(parser PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMARK_BINARY_DIR} ${GTKMM_INCLUDE_DIRS}) +target_link_libraries(parser PRIVATE libreweb-browser-lib-parser ${GTKMM_LIBRARIES} LibCommonMarker gtest_main) +add_test(NAME parser_test COMMAND parser) + +# Add target that runs all unit-tests +# The unit tests are running in xvfb (virtual frame buffer), allowing us +# to use GTK widgets. +add_custom_target(tests ALL + COMMAND xvfb-run env GTEST_COLOR=1 ${CMAKE_CTEST_COMMAND} --verbose --output-on-failure + DEPENDS draw file parser + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/tst + COMMENT "Execute all unit-tests" + VERBATIM +) diff --git a/tst/draw_test.cc b/tst/draw_test.cc new file mode 100644 index 0000000..4a8c00f --- /dev/null +++ b/tst/draw_test.cc @@ -0,0 +1,160 @@ +#include "draw.h" +#include "md-parser.h" +#include "mock-middleware.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include +namespace +{ + class DrawFixture : public testing::Test + { + protected: + void SetUp() override + { + Gtk::Application::create(); + } + }; + + TEST_F(DrawFixture, TestDrawSetText) + { + // Given + std::string text = "Hello world!"; + MockMiddleware middleware; + Draw draw(middleware); + + // When + draw.setText(text); + + // Then + ASSERT_EQ(draw.getText(), text); + } + + TEST_F(DrawFixture, TestDrawDocument) + { + // Given + std::string markdown = "**Hello** *world*"; + cmark_node* doc = Parser::parseContent(markdown); + + MockMiddleware middleware; + Draw draw(middleware); + + // When + draw.setDocument(doc); + + // Then + std::string result = draw.getText(); + ASSERT_EQ(result, "Hello world\n\n"); + } + + TEST_F(DrawFixture, TestDrawTextTags) + { + // Given + std::string italicTagName = "italic"; + std::string boldTagName = "bold"; + std::string heading1TagName = "heading1"; + std::string heading2TagName = "heading2"; + std::string heading3TagName = "heading3"; + std::string heading4TagName = "heading4"; + std::string heading5TagName = "heading5"; + std::string heading6TagName = "heading6"; + std::string strikethroughTagName = "strikethrough"; + std::string superscriptTagName = "superscript"; + std::string subscriptTagName = "subscript"; + std::string codeTagName = "code"; + std::string quoteTagName = "quote"; + std::string highlightTagName = "highlight"; + MockMiddleware middleware; + Draw draw(middleware); + + // When + auto tagTable = draw.get_buffer()->get_tag_table(); + auto properyItalic = tagTable->lookup(italicTagName); + auto properyBold = tagTable->lookup(boldTagName); + auto properyHeading1 = tagTable->lookup(heading1TagName); + auto properyHeading2 = tagTable->lookup(heading2TagName); + auto properyHeading3 = tagTable->lookup(heading3TagName); + auto properyHeading4 = tagTable->lookup(heading4TagName); + auto properyHeading5 = tagTable->lookup(heading5TagName); + auto properyHeading6 = tagTable->lookup(heading6TagName); + auto properyStrikethrough = tagTable->lookup(strikethroughTagName); + auto properySuperscript = tagTable->lookup(superscriptTagName); + auto properySubscript = tagTable->lookup(subscriptTagName); + auto properyCode = tagTable->lookup(codeTagName); + auto properyQuote = tagTable->lookup(quoteTagName); + auto properyHighlight = tagTable->lookup(highlightTagName); + + // Then + ASSERT_NE(properyItalic.get(), nullptr); + ASSERT_EQ(properyItalic->property_name().get_value(), italicTagName); + ASSERT_NE(properyBold.get(), nullptr); + ASSERT_EQ(properyBold->property_name().get_value(), boldTagName); + ASSERT_NE(properyHeading1.get(), nullptr); + ASSERT_EQ(properyHeading1->property_name().get_value(), heading1TagName); + ASSERT_NE(properyHeading2.get(), nullptr); + ASSERT_EQ(properyHeading2->property_name().get_value(), heading2TagName); + ASSERT_NE(properyHeading3.get(), nullptr); + ASSERT_EQ(properyHeading3->property_name().get_value(), heading3TagName); + ASSERT_NE(properyHeading4.get(), nullptr); + ASSERT_EQ(properyHeading4->property_name().get_value(), heading4TagName); + ASSERT_NE(properyHeading5.get(), nullptr); + ASSERT_EQ(properyHeading5->property_name().get_value(), heading5TagName); + ASSERT_NE(properyHeading6.get(), nullptr); + ASSERT_EQ(properyHeading6->property_name().get_value(), heading6TagName); + ASSERT_NE(properyStrikethrough.get(), nullptr); + ASSERT_EQ(properyStrikethrough->property_name().get_value(), strikethroughTagName); + ASSERT_NE(properySuperscript.get(), nullptr); + ASSERT_EQ(properySuperscript->property_name().get_value(), superscriptTagName); + ASSERT_NE(properySubscript.get(), nullptr); + ASSERT_EQ(properySubscript->property_name().get_value(), subscriptTagName); + ASSERT_NE(properyCode.get(), nullptr); + ASSERT_EQ(properyCode->property_name().get_value(), codeTagName); + ASSERT_NE(properyQuote.get(), nullptr); + ASSERT_EQ(properyQuote->property_name().get_value(), quoteTagName); + ASSERT_NE(properyHighlight.get(), nullptr); + ASSERT_EQ(properyHighlight->property_name().get_value(), highlightTagName); + } + + TEST_F(DrawFixture, TestDrawTextAttributes) + { + // Given + std::string markdown = "**bold**~~strikethrough~~^up^%down%`code`"; + gsize length = 0; + cmark_node* doc = Parser::parseContent(markdown); + MockMiddleware middleware; + Draw draw(middleware); + + // When + draw.setDocument(doc); + auto buffer = draw.get_buffer(); + // Using the built-in formatter + guint8* data = buffer->serialize(buffer, "application/x-gtk-text-buffer-rich-text", buffer->begin(), buffer->end(), length); + // Convert data to string + std::string stringData(data, data + length); + + // Then + // Bold attribute + std::string expectAttr1 = ""; + // Strikethrough attribute + std::string expectAttr2 = ""; + // Super-/subcript attribute + std::string expectAttr3 = + ""; + // Subscript attributes + std::string expectAttr5 = ""; + // Code attributes + std::string expectAttr6 = ""; + std::string expectAttr7 = ""; + std::string expectAttr8 = ""; + EXPECT_THAT(stringData, testing::HasSubstr(expectAttr1)); + EXPECT_THAT(stringData, testing::HasSubstr(expectAttr2)); + EXPECT_THAT(stringData, testing::HasSubstr(expectAttr3)); + EXPECT_THAT(stringData, testing::HasSubstr(expectAttr4)); + EXPECT_THAT(stringData, testing::HasSubstr(expectAttr5)); + EXPECT_THAT(stringData, testing::HasSubstr(expectAttr6)); + EXPECT_THAT(stringData, testing::HasSubstr(expectAttr7)); + EXPECT_THAT(stringData, testing::HasSubstr(expectAttr8)); + } +} // namespace diff --git a/tst/file_test.cc b/tst/file_test.cc new file mode 100644 index 0000000..1cb98d5 --- /dev/null +++ b/tst/file_test.cc @@ -0,0 +1,16 @@ +#include "file.h" +#include "gtest/gtest.h" +#include +namespace +{ + TEST(LibreWebTest, TestGetFilename) + { + // Given + std::string path = "/path/to/a/filename.sh"; + std::string expectedFilename = "filename.sh"; + // When + std::string filename = File::getFilename(path); + // Then + ASSERT_EQ(filename, expectedFilename); + } +} // namespace \ No newline at end of file diff --git a/tst/mock-middleware.h b/tst/mock-middleware.h new file mode 100644 index 0000000..a96393d --- /dev/null +++ b/tst/mock-middleware.h @@ -0,0 +1,28 @@ +#include "middleware-i.h" +#include "gmock/gmock.h" +#include + +struct cmark_node; + +class MockMiddleware : public MiddlewareInterface +{ +public: + MOCK_METHOD(void, + doRequest, + (const std::string& path, bool isSetAddressBar, bool isHistoryRequest, bool isDisableEditor, bool isParseContent), + (override)); + MOCK_METHOD(std::string, doAdd, (const std::string& path), (override)); + MOCK_METHOD(void, doWrite, (const std::string& path, bool isSetAddressAndTitle), (override)); + MOCK_METHOD(void, setContent, (const Glib::ustring& content), (override)); + MOCK_METHOD(Glib::ustring, getContent, (), (const, override)); + MOCK_METHOD(cmark_node*, parseContent, (), (const, override)); + MOCK_METHOD(void, resetContentAndPath, (), (override)); + MOCK_METHOD(std::size_t, getIPFSNumberOfPeers, (), (const, override)); + MOCK_METHOD(int, getIPFSRepoSize, (), (const, override)); + MOCK_METHOD(std::string, getIPFSRepoPath, (), (const, override)); + MOCK_METHOD(std::string, getIPFSIncomingRate, (), (const, override)); + MOCK_METHOD(std::string, getIPFSOutcomingRate, (), (const, override)); + MOCK_METHOD(std::string, getIPFSVersion, (), (const, override)); + MOCK_METHOD(std::string, getIPFSClientId, (), (const, override)); + MOCK_METHOD(std::string, getIPFSClientPublicKey, (), (const, override)); +}; diff --git a/tst/parser_test.cc b/tst/parser_test.cc new file mode 100644 index 0000000..530936b --- /dev/null +++ b/tst/parser_test.cc @@ -0,0 +1,50 @@ +#include "md-parser.h" +#include "gtest/gtest.h" +#include +#include +#include +namespace +{ + TEST(LibreWebTest, TestContentParser) + { + // Given + std::string markdown = "Jaja"; + uint16_t expectedDocument = CMARK_NODE_DOCUMENT; + + // When + cmark_node* doc = Parser::parseContent(markdown); + + // Then + ASSERT_EQ(doc->type, expectedDocument); + cmark_node_free(doc); + } + + TEST(LibreWebTest, TestHTMLRender) + { + // Given + std::string markdown = "_Italic_ **BOLD** ~~strike~~"; + std::string expectedHtml = "

Italic BOLD strike

\n"; + + // When + cmark_node* doc = Parser::parseContent(markdown); + std::string html = Parser::renderHTML(doc); + cmark_node_free(doc); + + // Then + ASSERT_EQ(html, expectedHtml); + } + + TEST(LibreWebTest, TestMarkdownRender) + { + // Given + std::string markdown = "**HOLA**"; + + // When + cmark_node* doc = Parser::parseContent(markdown); + std::string markdownAgain = Parser::renderMarkdown(doc); + cmark_node_free(doc); + + // Then + ASSERT_EQ(markdownAgain, markdown + "\n"); + } +} // namespace \ No newline at end of file