# SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
#
# SPDX-License-Identifier: CC0-1.0

# =============================================================================
# dde-shell frame/ core framework unit tests (GTest).
#
# New-workflow adaptation (DDE-107 round 3):
#   * Sources under test are compiled ONCE into a single OBJECT library
#     (frame_test_objects) and linked into every test executable, instead of
#     being recompiled per add_executable (round 1/2).  This keeps gcov
#     instrumentation in one place and yields a single dsLog definition
#     (Q_LOGGING_CATEGORY lives in pluginmetadata.cpp).
#   * protected/private members are exposed to test code via a scoped
#     `#define private public` block in listtotableproxymodeltests.cpp
#     (around the model header only), NOT via a global -D compile definition
#     (the latter breaks gtest/libstdc++ — see B1 review finding). Required
#     by listtotableproxymodeltests, which populates
#     ListToTableProxyModel::m_roles directly because the `roles` Q_PROPERTY is
#     only ever set from QML in production and QList<int> has no
#     Q_DECLARE_METATYPE in this repo.  kextracolumns and pluginfactory tests
#     use only public API and need no visibility macro.
#   * Coverage instrumentation + report generation are build-system targets:
#     option FRAME_BUILD_COVERAGE + custom target `frame_coverage`, emitting
#     into ${CMAKE_BINARY_DIR}/frame_coverage.
#   * Pattern (GTest + gtest_discover_tests, BUILD_TESTING gate) follows
#     tests/panels/dock/taskmanager.  Every TEST is registered with the
#     `frame_` ctest prefix so `ctest -R '^frame_'` runs only these tests.
# =============================================================================

find_package(GTest REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Gui Qml Test)

include(GoogleTest)

option(FRAME_BUILD_COVERAGE "Enable gcov coverage instrumentation for dde-shell frame unit tests" ON)

# ---- OBJECT library: all frame/ sources under test, compiled once ------------
# DS_LIB mirrors frame/CMakeLists.txt so the DS_SHARE export macro is correct
# while compiling these sources into the test objects.
# Sources are grouped by batch.  BUILD_WITH_X11 is intentionally NOT defined
# for this OBJECT lib so that dsutility.cpp uses the base (non-X11) Utility class.
add_library(frame_test_objects OBJECT
    # Batch 1 (PR #1703): pluginmetadata, appletdata, appletitemmodel, models, pluginfactory
    ${CMAKE_SOURCE_DIR}/frame/pluginmetadata.h
    ${CMAKE_SOURCE_DIR}/frame/pluginmetadata.cpp
    ${CMAKE_SOURCE_DIR}/frame/appletdata.h
    ${CMAKE_SOURCE_DIR}/frame/appletdata.cpp
    ${CMAKE_SOURCE_DIR}/frame/appletitemmodel.h
    ${CMAKE_SOURCE_DIR}/frame/appletitemmodel.cpp
    ${CMAKE_SOURCE_DIR}/frame/models/kextracolumnsproxymodel.h
    ${CMAKE_SOURCE_DIR}/frame/models/kextracolumnsproxymodel.cpp
    ${CMAKE_SOURCE_DIR}/frame/models/listtotableproxymodel.h
    ${CMAKE_SOURCE_DIR}/frame/models/listtotableproxymodel.cpp
    ${CMAKE_SOURCE_DIR}/frame/pluginfactory.h
    ${CMAKE_SOURCE_DIR}/frame/pluginfactory.cpp

    # Batch N+1: dstypes, applet, appletproxy, dsutility
    ${CMAKE_SOURCE_DIR}/frame/dstypes.h
    ${CMAKE_SOURCE_DIR}/frame/dstypes.cpp
    ${CMAKE_SOURCE_DIR}/frame/applet.h
    ${CMAKE_SOURCE_DIR}/frame/applet.cpp
    ${CMAKE_SOURCE_DIR}/frame/appletproxy.h
    ${CMAKE_SOURCE_DIR}/frame/appletproxy.cpp
    ${CMAKE_SOURCE_DIR}/frame/dsutility.h
    ${CMAKE_SOURCE_DIR}/frame/dsutility.cpp
)
target_compile_definitions(frame_test_objects PRIVATE DS_LIB)
target_include_directories(frame_test_objects PUBLIC
    ${CMAKE_SOURCE_DIR}/frame
    ${CMAKE_SOURCE_DIR}/frame/models
)
# Dtk::Core is needed by applet.cpp/appletproxy.cpp/dsutility.cpp (DObject/DObjectPrivate).
# Qt::Gui is needed by dsutility.cpp (QGuiApplication/QWindow).
# Both are already found by the top-level CMakeLists.txt.
target_link_libraries(frame_test_objects PUBLIC
    Qt${QT_VERSION_MAJOR}::Core
    Qt${QT_VERSION_MAJOR}::Qml
    Qt${QT_VERSION_MAJOR}::Gui
    Dtk${DTK_VERSION_MAJOR}::Core
)

# --- B1 workaround (test-side, no src change) -------------------------------
# dsutility.cpp uses QGuiApplication::platformName() / qGuiApp->allWindows() but
# does NOT directly #include <QGuiApplication>. In production, BUILD_WITH_X11=ON
# (default) pulls in utility_x11_p.h which transitively includes QGuiApplication.
# This test OBJECT lib intentionally does NOT define BUILD_WITH_X11 (to avoid XCB
# cascade), which cuts that transitive include. As a test-side workaround we
# force-include <qguiapplication.h> into every TU of the OBJECT lib so dsutility.cpp
# compiles without touching src. NB: this is a workaround for a src robustness
# gap (dsutility.cpp should directly include <QGuiApplication>); a separate src
# defect issue should fix it, after which this block can be removed.
#
# Use find_path to resolve the absolute header path (avoid hardcoding), then pass
# -include and the path as TWO separate list elements (CMake list separator) —
# writing "-include<path>" would be misparsed ("<" as redirection by make/shell).
find_path(QT6_GUIAPP_INCLUDE_DIR qguiapplication.h
    PATHS ${Qt${QT_VERSION_MAJOR}Gui_INCLUDE_DIRS}
    NO_DEFAULT_PATH
)
if(QT6_GUIAPP_INCLUDE_DIR)
    target_compile_options(frame_test_objects PRIVATE
        "-include" "${QT6_GUIAPP_INCLUDE_DIR}/qguiapplication.h")
else()
    # Fallback: Qt6 may expose headers under a QtGui/ subdirectory or via the
    # imported target's INTERFACE_INCLUDE_DIRECTORIES. Resolve via the target.
    get_target_property(_qt6gui_incdirs Qt${QT_VERSION_MAJOR}::Gui
        INTERFACE_INCLUDE_DIRECTORIES)
    find_path(QT6_GUIAPP_INCLUDE_DIR qguiapplication.h
        PATHS ${_qt6gui_incdirs}
        NO_DEFAULT_PATH
    )
    if(QT6_GUIAPP_INCLUDE_DIR)
        target_compile_options(frame_test_objects PRIVATE
            "-include" "${QT6_GUIAPP_INCLUDE_DIR}/qguiapplication.h")
    else()
        message(WARNING "qguiapplication.h not found in Qt${QT_VERSION_MAJOR}::Gui include dirs; "
            "dsutility.cpp may fail to compile. Set QT6_GUIAPP_INCLUDE_DIR manually.")
    endif()
endif()

# ---- Helper: declare a frame test executable with the standard link set -------
set(FRAME_TEST_TARGETS "")
function(frame_add_test NAME)
    add_executable(${NAME} ${ARGN})
    target_link_libraries(${NAME} PRIVATE
        GTest::GTest
        GTest::Main
        Qt${QT_VERSION_MAJOR}::Core
        Qt${QT_VERSION_MAJOR}::Gui
        Qt${QT_VERSION_MAJOR}::Test
        frame_test_objects
    )
    gtest_discover_tests(${NAME} TEST_PREFIX "frame_")
    list(APPEND FRAME_TEST_TARGETS ${NAME})
    set(FRAME_TEST_TARGETS "${FRAME_TEST_TARGETS}" PARENT_SCOPE)
endfunction()

# ---- Test executables --------------------------------------------------------
# Pre-existing (round 1/2, reviewed & 45/45 passing) — test sources unchanged.
frame_add_test(pluginmetadata_tests   pluginmetadatatests.cpp)
frame_add_test(appletdata_tests       appletdatatests.cpp)
frame_add_test(appletitemmodel_tests  appletitemmodeltests.cpp)

# New (round 3) — extend coverage to previously-untested frame/ modules.
frame_add_test(proxymodel_tests
    kextracolumnsproxymodeltests.cpp
    listtotableproxymodeltests.cpp
)
frame_add_test(pluginfactory_tests    pluginfactorytests.cpp)

# Batch N+1: dstypes, applet, appletproxy, dsutility
frame_add_test(types_tests            typestests.cpp)
frame_add_test(applet_tests           applettests.cpp)
frame_add_test(appletproxy_tests      appletproxytests.cpp)
frame_add_test(utility_tests          utilitytests.cpp)

# Visibility: private-member access for ListToTableProxyModel::m_roles /
# m_sourceColumn is done via a scoped `#define private public` block in
# listtotableproxymodeltests.cpp itself (around the model header only), NOT
# via a global -Dprivate=public compile definition — the latter breaks
# gtest/libstdc++ (<sstream>/<any> access redeclaration). kextracolumns and
# pluginfactory tests use only public API and need no visibility macro.

# ---- Coverage instrumentation + report generation (build-system target) -------
if(FRAME_BUILD_COVERAGE)
    message(STATUS "frame tests: coverage instrumentation ENABLED (FRAME_BUILD_COVERAGE=ON)")
    # Instrument the single OBJECT lib (covers all sources under test) and the
    # test executables (so the link step pulls in libgcov).
    target_compile_options(frame_test_objects PRIVATE -fprofile-arcs -ftest-coverage -O0 -g)
    foreach(_t IN LISTS FRAME_TEST_TARGETS)
        target_compile_options(${_t} PRIVATE -fprofile-arcs -ftest-coverage -O0 -g)
        target_link_options(${_t} PRIVATE -fprofile-arcs)
    endforeach()
endif()

# ---- coverage: execute frame tests and generate coverage report --------------
find_program(LCOV_BIN lcov)
find_program(GENHTML_BIN genhtml)
set(_run_test_coverage "")
if(LCOV_BIN AND GENHTML_BIN)
    set(FRAME_COVERAGE_DIR "${CMAKE_BINARY_DIR}/frame_coverage")
    set(_run_test_coverage
        COMMAND ${CMAKE_COMMAND} -E make_directory "${FRAME_COVERAGE_DIR}"
        COMMAND ${LCOV_BIN} --capture --directory "${CMAKE_BINARY_DIR}"
                --rc lcov_branch_coverage=1
                --output-file "${FRAME_COVERAGE_DIR}/frame.info"
        COMMAND ${LCOV_BIN} --extract "${FRAME_COVERAGE_DIR}/frame.info"
                --rc lcov_branch_coverage=1
                "${CMAKE_SOURCE_DIR}/frame/*"
                "${CMAKE_SOURCE_DIR}/frame/models/*"
                -o "${FRAME_COVERAGE_DIR}/frame_filtered.info"
        COMMAND ${GENHTML_BIN} "${FRAME_COVERAGE_DIR}/frame_filtered.info"
                -o "${FRAME_COVERAGE_DIR}/html" --branch-coverage
        COMMAND ${CMAKE_COMMAND} -E echo "=== Frame coverage report: ${FRAME_COVERAGE_DIR}/html/index.html ==="
    )
endif()
add_custom_target(frame_coverage
    DEPENDS ${FRAME_TEST_TARGETS}
    COMMAND ${CMAKE_CTEST_COMMAND} --test-dir "${CMAKE_BINARY_DIR}" -R "^frame_" --output-on-failure
    ${_run_test_coverage}
    WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
    COMMENT "Run frame unit tests and generate lcov branch-coverage report"
    VERBATIM
)
