mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 17:43:23 -08:00
Compare commits
4 commits
7c8b696f9d
...
e6847b0813
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6847b0813 | ||
|
|
c7a0c7c378 | ||
|
|
9511593724 | ||
|
|
7dd21f60ae |
34 changed files with 681 additions and 4 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -4,3 +4,6 @@
|
|||
[submodule "doxygen-awesome-css"]
|
||||
path = doc/doxygen/theme
|
||||
url = https://github.com/jothepro/doxygen-awesome-css.git
|
||||
[submodule "external/qttesting"]
|
||||
path = external/qttesting
|
||||
url = https://github.com/Kitware/QtTesting.git
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ option(WITH_CLIENT "Build Cockatrice client" ON)
|
|||
option(WITH_ORACLE "Build Cockatrice card database tool (Oracle)" ON)
|
||||
# Compile Servatrice
|
||||
option(WITH_SERVER "Build Cockatrice server (Servatrice)" OFF)
|
||||
# Compile GUI tests (QtTesting widget-level playback)
|
||||
option(WITH_GUI_TEST "Build the QtTesting-based GUI test harness" ON)
|
||||
# Compile tests
|
||||
option(TEST "Build tests" OFF)
|
||||
# Build with AddressSanitizer and UndefinedBehaviorSanitizer
|
||||
|
|
@ -29,6 +31,14 @@ option(ENABLE_COVERAGE "Build instrumented for GCC line coverage" OFF)
|
|||
# Use vcpkg regardless of OS
|
||||
option(USE_VCPKG "Use vcpkg regardless of OS" OFF)
|
||||
|
||||
# The GUI test harness lives in a submodule; a checkout without it must still configure.
|
||||
if(WITH_GUI_TEST AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/qttesting/CMakeLists.txt")
|
||||
message(WARNING "WITH_GUI_TEST is ON but external/qttesting is missing, so GUI tests are disabled.\n"
|
||||
"Run 'git submodule update --init external/qttesting' to enable them."
|
||||
)
|
||||
set(WITH_GUI_TEST OFF)
|
||||
endif()
|
||||
|
||||
# Default to "Release" build type
|
||||
# User-provided value for CMAKE_BUILD_TYPE must be checked before the PROJECT() call
|
||||
if(DEFINED CMAKE_BUILD_TYPE)
|
||||
|
|
@ -455,6 +465,34 @@ if(WITH_ORACLE)
|
|||
set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
|
||||
endif()
|
||||
|
||||
if(WITH_GUI_TEST)
|
||||
set(QtTesting_QT_VERSION "6")
|
||||
# QtTesting registers its own unit tests when BUILD_TESTING is on. They target the
|
||||
# Qt version the harness was written for and fail on Qt 6.11 (toolbutton
|
||||
# long-press recording), and they are not Cockatrice's tests, so suppress them
|
||||
# for the duration of the subdirectory instead of painting ctest red.
|
||||
if(DEFINED BUILD_TESTING)
|
||||
set(_cockatrice_saved_build_testing ${BUILD_TESTING})
|
||||
endif()
|
||||
set(BUILD_TESTING
|
||||
OFF
|
||||
CACHE BOOL "Build the testing tree." FORCE
|
||||
)
|
||||
add_subdirectory(external/qttesting)
|
||||
if(DEFINED _cockatrice_saved_build_testing)
|
||||
set(BUILD_TESTING
|
||||
${_cockatrice_saved_build_testing}
|
||||
CACHE BOOL "Build the testing tree." FORCE
|
||||
)
|
||||
else()
|
||||
unset(BUILD_TESTING CACHE)
|
||||
endif()
|
||||
# cl.exe has no warnings-as-errors here, and it rejects -Wno-error with D8021.
|
||||
if(NOT MSVC)
|
||||
target_compile_options(qttesting PRIVATE -Wno-error)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(TEST)
|
||||
include(CTest)
|
||||
add_subdirectory(tests)
|
||||
|
|
|
|||
52
Testing/README.md
Normal file
52
Testing/README.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# GUI tests
|
||||
|
||||
Widget-level playback tests for the Cockatrice client, driven by
|
||||
[QtTesting](https://github.com/Kitware/QtTesting) (`external/qttesting`, a submodule).
|
||||
Each `*_test.xml` file is a list of events that QtTesting replays against the running
|
||||
application; a test passes when every event is handled without error.
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
git submodule update --init external/qttesting # once
|
||||
cmake -S . -B build/debug -DCMAKE_BUILD_TYPE=Debug -DWITH_SERVER=0 -DWITH_GUI_TEST=ON
|
||||
cmake --build build/debug -j 24
|
||||
|
||||
./Testing/run_test.sh --all # headless, offscreen
|
||||
./Testing/run_test.sh Testing/smoke_test.xml # one file
|
||||
./Testing/run_test.sh --visual Testing/smoke_test.xml # shows the window
|
||||
```
|
||||
|
||||
`WITH_GUI_TEST` is on by default; a checkout without the submodule configures with a
|
||||
warning and no harness. `COCKATRICE_BIN` points the runner at another build, and
|
||||
`TEST_TIMEOUT` (seconds, default 180) bounds a single test — a failing event inside a
|
||||
modal dialog can otherwise leave the process alive. The client also hard-exits a few
|
||||
seconds after playback returns, whatever the result.
|
||||
|
||||
The tests are **not** part of `ctest`: they need a card database and a developer's
|
||||
settings, so they stay a local loop for now.
|
||||
|
||||
## Caveats
|
||||
|
||||
- **Not hermetic.** The client reads the local settings and card database, so a test can
|
||||
pass on one machine and fail on another. Avoid asserting things that restored
|
||||
workspace state or user preferences decide (for example the number of open tabs).
|
||||
- **English labels only.** Menu entries are addressed by their (translated) text, so the
|
||||
tests expect the source language. Widgets are addressed by objectName paths, which are
|
||||
stable across translations.
|
||||
- Actions that open a dialog are followed by a `pause` event: playback continues in the
|
||||
dialog's own event loop, and the pause gives the UI time to build itself.
|
||||
- Widget paths include Qt-internal names where a QTabWidget is involved
|
||||
(`qt_tabwidget_stackedwidget`), and the deck views in a game are indexed
|
||||
(`tabbedDeckView_0`, `tabbedDeckView_1`).
|
||||
|
||||
## Writing a test
|
||||
|
||||
A test is a `<events>` list. Widgets are named by their path from the main window, e.g.
|
||||
`MainWindow/tabSupervisor/stackedWidget/TabGame/phasesMenu`. Useful commands:
|
||||
|
||||
| Command | Arguments | Effect |
|
||||
|---------|-----------|--------|
|
||||
| `activate` | action or menu name | triggers a QAction (menus are opened first) |
|
||||
| `key` | Qt key code | presses that key on the focused widget |
|
||||
| `pause` | milliseconds | waits, letting the UI settle |
|
||||
4
Testing/deck_editor_open_test.xml
Normal file
4
Testing/deck_editor_open_test.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<events>
|
||||
<!-- Open a new deck editor tab from the home page -->
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabHome/homeWidget/homeButtonsBox/homeCreateNewDeckButton" command="activate" arguments="" />
|
||||
</events>
|
||||
14
Testing/deck_editor_search_test.xml
Normal file
14
Testing/deck_editor_search_test.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<events>
|
||||
<!-- Open a new visual deck editor tab from the home page -->
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabHome/homeWidget/homeButtonsBox/homeCreateNewDeckButton" command="activate" arguments="" />
|
||||
<!-- Type "Lightning" into the search box (keycodes for L-i-g-h-t-n-i-n-g) -->
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="76" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="105" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="103" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="104" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="116" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="110" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="105" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="110" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="103" />
|
||||
</events>
|
||||
11
Testing/dialog_lifecycle_test.xml
Normal file
11
Testing/dialog_lifecycle_test.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<events>
|
||||
<!-- Open and close Settings dialog (modal exec) -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="cockatriceMenu" />
|
||||
<event widget="MainWindow/menubar/cockatriceMenu" command="activate" arguments="actionSettings" />
|
||||
<event widget="MainWindow/settingsDialog" command="key" arguments="16777216" />
|
||||
|
||||
<!-- Open and close About dialog (modal exec) -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="helpMenu" />
|
||||
<event widget="MainWindow/menubar/helpMenu" command="activate" arguments="&About Cockatrice" />
|
||||
<event widget="MainWindow/aboutDialog" command="key" arguments="16777216" />
|
||||
</events>
|
||||
45
Testing/integration_test.xml
Normal file
45
Testing/integration_test.xml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<events>
|
||||
<!-- Integration test: exercises multiple tiers in sequence -->
|
||||
|
||||
<!-- Tier 2: Open and close all menus -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="cockatriceMenu" />
|
||||
<event widget="MainWindow" command="key" arguments="16777216" />
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="dbMenu" />
|
||||
<event widget="MainWindow" command="key" arguments="16777216" />
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="tabsMenu" />
|
||||
<event widget="MainWindow" command="key" arguments="16777216" />
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="helpMenu" />
|
||||
<event widget="MainWindow" command="key" arguments="16777216" />
|
||||
|
||||
<!-- Tier 2: Open and close About (modal) -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="helpMenu" />
|
||||
<event widget="MainWindow/menubar/helpMenu" command="activate" arguments="&About Cockatrice" />
|
||||
<event widget="MainWindow/aboutDialog" command="key" arguments="16777216" />
|
||||
|
||||
<!-- Tier 4: Open and close ViewLog (non-modal) -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="helpMenu" />
|
||||
<event widget="MainWindow/menubar/helpMenu" command="activate" arguments="View &Debug Log" />
|
||||
<event widget="MainWindow/viewLogDialog" command="key" arguments="16777216" />
|
||||
|
||||
<!-- Tier 4: Open and close Tip of the Day (non-modal) -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="helpMenu" />
|
||||
<event widget="MainWindow/menubar/helpMenu" command="activate" arguments="&Tip of the Day" />
|
||||
<event widget="MainWindow/tipOfTheDayDialog" command="key" arguments="16777216" />
|
||||
|
||||
<!-- Tier 5: Settings search (state interaction) -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="cockatriceMenu" />
|
||||
<event widget="MainWindow/menubar/cockatriceMenu" command="activate" arguments="actionSettings" />
|
||||
<event widget="MainWindow/settingsDialog/settingsView/settingsSearchEdit" command="key" arguments="100" />
|
||||
<event widget="MainWindow/settingsDialog/settingsView/settingsSearchEdit" command="key" arguments="101" />
|
||||
<event widget="MainWindow/settingsDialog/settingsView/settingsSearchEdit" command="key" arguments="98" />
|
||||
<event widget="MainWindow/settingsDialog/settingsView/settingsSearchEdit" command="key" arguments="117" />
|
||||
<event widget="MainWindow/settingsDialog/settingsOkButton" command="activate" arguments="" />
|
||||
|
||||
<!-- Tier 3: Open deck editor from home and type in search -->
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabHome/homeWidget/homeButtonsBox/homeCreateNewDeckButton" command="activate" arguments="" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="76" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="105" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="103" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="104" />
|
||||
<event widget="MainWindow/tabSupervisor/stackedWidget/TabDeckEditorVisual/databaseDisplayDock/databaseDisplayDockContents/databaseDisplayWidget/searchEdit" command="key" arguments="116" />
|
||||
</events>
|
||||
19
Testing/menu_open_close_test.xml
Normal file
19
Testing/menu_open_close_test.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<events>
|
||||
<!-- Test 1: Open and close each top-level menu -->
|
||||
|
||||
<!-- Cockatrice menu -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="cockatriceMenu" />
|
||||
<event widget="MainWindow" command="key" arguments="16777216" />
|
||||
|
||||
<!-- Database menu -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="dbMenu" />
|
||||
<event widget="MainWindow" command="key" arguments="16777216" />
|
||||
|
||||
<!-- Tabs menu -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="tabsMenu" />
|
||||
<event widget="MainWindow" command="key" arguments="16777216" />
|
||||
|
||||
<!-- Help menu -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="helpMenu" />
|
||||
<event widget="MainWindow" command="key" arguments="16777216" />
|
||||
</events>
|
||||
6
Testing/nonmodal_manage_sets_test.xml
Normal file
6
Testing/nonmodal_manage_sets_test.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<events>
|
||||
<!-- Test non-modal dialog lifecycle: open and close Manage Sets (QMainWindow) -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="dbMenu" />
|
||||
<event widget="MainWindow/menubar/dbMenu" command="activate" arguments="&Manage sets..." />
|
||||
<event widget="MainWindow/manageSetsWindow" command="key" arguments="16777216" />
|
||||
</events>
|
||||
6
Testing/nonmodal_tip_test.xml
Normal file
6
Testing/nonmodal_tip_test.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<events>
|
||||
<!-- Test non-modal dialog lifecycle: open and close Tip of the Day -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="helpMenu" />
|
||||
<event widget="MainWindow/menubar/helpMenu" command="activate" arguments="&Tip of the Day" />
|
||||
<event widget="MainWindow/tipOfTheDayDialog" command="key" arguments="16777216" />
|
||||
</events>
|
||||
6
Testing/nonmodal_view_log_test.xml
Normal file
6
Testing/nonmodal_view_log_test.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<events>
|
||||
<!-- Test non-modal dialog lifecycle: open and close ViewLog -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="helpMenu" />
|
||||
<event widget="MainWindow/menubar/helpMenu" command="activate" arguments="View &Debug Log" />
|
||||
<event widget="MainWindow/viewLogDialog" command="key" arguments="16777216" />
|
||||
</events>
|
||||
101
Testing/run_test.sh
Executable file
101
Testing/run_test.sh
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
#!/bin/bash
|
||||
# Run QtTesting XML test files against the Cockatrice build.
|
||||
#
|
||||
# Usage:
|
||||
# ./Testing/run_test.sh [--visual] [--all | <test-file.xml> ...]
|
||||
#
|
||||
# Options:
|
||||
# --visual Run with native display (shows the GUI window).
|
||||
# Default is headless (QT_QPA_PLATFORM=offscreen).
|
||||
# --all Run every *.xml file in Testing/.
|
||||
#
|
||||
# Environment:
|
||||
# COCKATRICE_BIN Path to the client binary (default: build/debug/cockatrice/cockatrice).
|
||||
# TEST_TIMEOUT Seconds a single test may run before it is killed (default: 180).
|
||||
#
|
||||
# Requires the debug build to exist at build/debug/cockatrice/cockatrice.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
COCKATRICE="${COCKATRICE_BIN:-$REPO_ROOT/build/debug/cockatrice/cockatrice}"
|
||||
TEST_DIR="$REPO_ROOT/Testing"
|
||||
TEST_TIMEOUT="${TEST_TIMEOUT:-180}"
|
||||
|
||||
if [ ! -f "$COCKATRICE" ]; then
|
||||
echo "Error: $COCKATRICE not found. Build first:" >&2
|
||||
echo " cmake --build build/debug" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VISUAL=0
|
||||
if [ "${1:-}" = "--visual" ]; then
|
||||
VISUAL=1
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ "${1:-}" = "--all" ]; then
|
||||
shift
|
||||
TEST_FILES=("$TEST_DIR"/*.xml)
|
||||
if [ ${#TEST_FILES[@]} -eq 0 ]; then
|
||||
echo "No *.xml files found in $TEST_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
elif [ $# -ge 1 ]; then
|
||||
TEST_FILES=("$@")
|
||||
else
|
||||
echo "Usage: $0 [--visual] [--all | <test-file.xml> ...]" >&2
|
||||
echo " Run a single test: $0 Testing/smoke_test.xml" >&2
|
||||
echo " Run all tests: $0 --all" >&2
|
||||
echo " Run visually: $0 --visual Testing/smoke_test.xml" >&2
|
||||
echo " Run all visually: $0 --visual --all" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
FAILED_TESTS=()
|
||||
|
||||
run_one() {
|
||||
local file="$1"
|
||||
local name
|
||||
name="$(basename "$file")"
|
||||
|
||||
if [ "$VISUAL" -eq 1 ]; then
|
||||
if timeout --foreground "$TEST_TIMEOUT" "$COCKATRICE" --no-connect --test "$file"; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
if timeout --foreground "$TEST_TIMEOUT" env QT_QPA_PLATFORM=offscreen "$COCKATRICE" --no-connect --test "$file"; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
for f in "${TEST_FILES[@]}"; do
|
||||
name="$(basename "$f")"
|
||||
printf "%-40s " "$name"
|
||||
if run_one "$f"; then
|
||||
echo "PASS"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "FAIL"
|
||||
FAIL=$((FAIL + 1))
|
||||
FAILED_TESTS+=("$name")
|
||||
fi
|
||||
done
|
||||
|
||||
TOTAL=$((PASS + FAIL))
|
||||
echo ""
|
||||
echo "Results: $PASS/$TOTAL passed, $FAIL failed"
|
||||
if [ "$FAIL" -gt 0 ]; then
|
||||
echo "Failed:"
|
||||
for t in "${FAILED_TESTS[@]}"; do
|
||||
echo " $t"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
6
Testing/settings_dialog_test.xml
Normal file
6
Testing/settings_dialog_test.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<events>
|
||||
<!-- Open Settings dialog via Cockatrice menu, then close with Escape -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="cockatriceMenu" />
|
||||
<event widget="MainWindow/menubar/cockatriceMenu" command="activate" arguments="actionSettings" />
|
||||
<event widget="MainWindow/settingsDialog" command="key" arguments="16777216" />
|
||||
</events>
|
||||
13
Testing/settings_search_test.xml
Normal file
13
Testing/settings_search_test.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<events>
|
||||
<!-- Tier 5: State verification — open Settings, search for a page, close -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="cockatriceMenu" />
|
||||
<event widget="MainWindow/menubar/cockatriceMenu" command="activate" arguments="actionSettings" />
|
||||
<!-- Type "sound" in the settings search to filter pages -->
|
||||
<event widget="MainWindow/settingsDialog/settingsView/settingsSearchEdit" command="key" arguments="115" />
|
||||
<event widget="MainWindow/settingsDialog/settingsView/settingsSearchEdit" command="key" arguments="111" />
|
||||
<event widget="MainWindow/settingsDialog/settingsView/settingsSearchEdit" command="key" arguments="117" />
|
||||
<event widget="MainWindow/settingsDialog/settingsView/settingsSearchEdit" command="key" arguments="110" />
|
||||
<event widget="MainWindow/settingsDialog/settingsView/settingsSearchEdit" command="key" arguments="100" />
|
||||
<!-- Close settings -->
|
||||
<event widget="MainWindow/settingsDialog/settingsOkButton" command="activate" arguments="" />
|
||||
</events>
|
||||
8
Testing/smoke_test.xml
Normal file
8
Testing/smoke_test.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<events>
|
||||
<!-- Open Help menu by activating it on the menu bar -->
|
||||
<event widget="MainWindow/menubar" command="activate" arguments="helpMenu" />
|
||||
<!-- Click About Cockatrice (opens modal QMessageBox) -->
|
||||
<event widget="MainWindow/menubar/helpMenu" command="activate" arguments="&About Cockatrice" />
|
||||
<!-- Close the modal dialog by pressing Escape (Qt::Key_Escape = 16777216) -->
|
||||
<event widget="MainWindow/aboutDialog" command="key" arguments="16777216" />
|
||||
</events>
|
||||
|
|
@ -562,6 +562,14 @@ target_link_libraries(
|
|||
|
||||
cockatrice_target_enable_crash_reporting(cockatrice)
|
||||
|
||||
if(WITH_GUI_TEST)
|
||||
# recording.cpp translates between XML test files and the QtTesting event pipeline;
|
||||
# it needs the QtTesting headers, so it only exists with the harness.
|
||||
target_sources(cockatrice PRIVATE src/interface/recording.cpp)
|
||||
target_link_libraries(cockatrice PRIVATE QtTesting)
|
||||
target_compile_definitions(cockatrice PRIVATE WITH_GUI_TEST)
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
if(APPLE)
|
||||
set(MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME}")
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
# or set <category> = false to disable logging
|
||||
|
||||
#main = true
|
||||
#recording = true
|
||||
#qt_translator = true
|
||||
#window_main.* = true
|
||||
#release_channel = true
|
||||
|
|
|
|||
102
cockatrice/src/interface/recording.cpp
Normal file
102
cockatrice/src/interface/recording.cpp
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#include "recording.h"
|
||||
|
||||
#include "pqEventTypes.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
XMLEventObserver::XMLEventObserver(QObject *parent) : pqEventObserver(parent)
|
||||
{
|
||||
}
|
||||
|
||||
XMLEventObserver::~XMLEventObserver() = default;
|
||||
|
||||
void XMLEventObserver::setStream(QTextStream *stream)
|
||||
{
|
||||
if (xmlStream) {
|
||||
xmlStream->writeEndElement();
|
||||
xmlStream->writeEndDocument();
|
||||
xmlStream.reset();
|
||||
}
|
||||
if (Stream) {
|
||||
*Stream << xmlString;
|
||||
}
|
||||
xmlString.clear();
|
||||
pqEventObserver::setStream(stream);
|
||||
if (Stream) {
|
||||
xmlStream.reset(new QXmlStreamWriter(&xmlString));
|
||||
xmlStream->setAutoFormatting(true);
|
||||
xmlStream->writeStartDocument();
|
||||
xmlStream->writeStartElement(QStringLiteral("events"));
|
||||
}
|
||||
}
|
||||
|
||||
void XMLEventObserver::onRecordEvent(const QString &widget,
|
||||
const QString &command,
|
||||
const QString &arguments,
|
||||
const int &eventType)
|
||||
{
|
||||
if (!xmlStream) {
|
||||
return;
|
||||
}
|
||||
|
||||
xmlStream->writeStartElement(QStringLiteral("event"));
|
||||
xmlStream->writeAttribute(QStringLiteral("widget"), widget);
|
||||
if (eventType == pqEventTypes::ACTION_EVENT) {
|
||||
xmlStream->writeAttribute(QStringLiteral("command"), command);
|
||||
} else { // pqEventTypes::CHECK_EVENT
|
||||
xmlStream->writeAttribute(QStringLiteral("property"), command);
|
||||
}
|
||||
xmlStream->writeAttribute(QStringLiteral("arguments"), arguments);
|
||||
xmlStream->writeEndElement();
|
||||
}
|
||||
|
||||
XMLEventSource::XMLEventSource(QObject *parent) : pqEventSource(parent)
|
||||
{
|
||||
}
|
||||
|
||||
XMLEventSource::~XMLEventSource() = default;
|
||||
|
||||
void XMLEventSource::setContent(const QString &xmlFileName)
|
||||
{
|
||||
xmlStream.reset();
|
||||
|
||||
QFile xml(xmlFileName);
|
||||
if (!xml.open(QIODevice::ReadOnly)) {
|
||||
qCWarning(RecordingLog) << "Could not read test file" << xmlFileName;
|
||||
return;
|
||||
}
|
||||
|
||||
xmlStream.reset(new QXmlStreamReader(xml.readAll()));
|
||||
}
|
||||
|
||||
int XMLEventSource::getNextEvent(QString &widget, QString &command, QString &arguments, int &eventType)
|
||||
{
|
||||
if (!xmlStream) {
|
||||
return DONE;
|
||||
}
|
||||
|
||||
while (!xmlStream->atEnd()) {
|
||||
if (xmlStream->readNext() != QXmlStreamReader::StartElement || xmlStream->name() != QStringLiteral("event")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QXmlStreamAttributes attributes = xmlStream->attributes();
|
||||
eventType = pqEventTypes::ACTION_EVENT;
|
||||
widget = attributes.value(QStringLiteral("widget")).toString();
|
||||
command = attributes.value(QStringLiteral("command")).toString();
|
||||
if (command.isEmpty()) {
|
||||
// A recorded check event carries a property instead of a command.
|
||||
command = attributes.value(QStringLiteral("property")).toString();
|
||||
}
|
||||
arguments = attributes.value(QStringLiteral("arguments")).toString();
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
if (xmlStream->hasError()) {
|
||||
qCWarning(RecordingLog) << "Malformed test file:" << xmlStream->errorString();
|
||||
}
|
||||
return DONE;
|
||||
}
|
||||
54
cockatrice/src/interface/recording.h
Normal file
54
cockatrice/src/interface/recording.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef COCKATRICE_RECORDING_H
|
||||
#define COCKATRICE_RECORDING_H
|
||||
|
||||
#include "pqEventObserver.h"
|
||||
#include "pqEventSource.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QScopedPointer>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(RecordingLog, "recording");
|
||||
|
||||
class QXmlStreamReader;
|
||||
class QXmlStreamWriter;
|
||||
|
||||
/**
|
||||
* @brief Writes a session recording as an XML test file.
|
||||
*
|
||||
* The events are buffered in memory and flushed to the stream when recording
|
||||
* ends, because the stream is handed over once the file name is known.
|
||||
*/
|
||||
class XMLEventObserver : public pqEventObserver
|
||||
{
|
||||
QScopedPointer<QXmlStreamWriter> xmlStream;
|
||||
QString xmlString;
|
||||
|
||||
public:
|
||||
explicit XMLEventObserver(QObject *parent);
|
||||
~XMLEventObserver() override;
|
||||
|
||||
protected:
|
||||
void setStream(QTextStream *stream) override;
|
||||
void onRecordEvent(const QString &widget,
|
||||
const QString &command,
|
||||
const QString &arguments,
|
||||
const int &eventType) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Plays the events of an XML test file back, one at a time.
|
||||
*/
|
||||
class XMLEventSource : public pqEventSource
|
||||
{
|
||||
QScopedPointer<QXmlStreamReader> xmlStream;
|
||||
|
||||
public:
|
||||
explicit XMLEventSource(QObject *parent);
|
||||
~XMLEventSource() override;
|
||||
|
||||
protected:
|
||||
void setContent(const QString &xmlFileName) override;
|
||||
int getNextEvent(QString &widget, QString &command, QString &arguments, int &eventType) override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_RECORDING_H
|
||||
|
|
@ -33,6 +33,7 @@ class QWidget;
|
|||
|
||||
DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
setObjectName("connectDialog");
|
||||
previousHostButton = new QRadioButton(tr("Known Hosts"), this);
|
||||
previousHosts = new QComboBox(this);
|
||||
|
||||
|
|
@ -85,6 +86,11 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent)
|
|||
passwordLabel->setBuddy(passwordEdit);
|
||||
passwordEdit->setEchoMode(QLineEdit::Password);
|
||||
|
||||
hostEdit->setObjectName("connectHostEdit");
|
||||
portEdit->setObjectName("connectPortEdit");
|
||||
playernameEdit->setObjectName("connectPlayernameEdit");
|
||||
passwordEdit->setObjectName("connectPasswordEdit");
|
||||
|
||||
savePasswordCheckBox = new QCheckBox(tr("&Save password"));
|
||||
|
||||
autoConnectCheckBox = new QCheckBox(tr("A&uto connect"));
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class QWidget;
|
|||
|
||||
DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nullptr)
|
||||
{
|
||||
setObjectName("editTokensDialog");
|
||||
nameLabel = new QLabel(tr("&Name:"));
|
||||
nameEdit = new QLineEdit;
|
||||
nameEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
|
||||
WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
|
||||
{
|
||||
setObjectName("manageSetsWindow");
|
||||
setOrderIsSorted = false;
|
||||
|
||||
// left toolbar
|
||||
|
|
@ -221,6 +222,7 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
|
|||
|
||||
WndSets::~WndSets()
|
||||
{
|
||||
disconnect(view->header(), &QHeaderView::geometriesChanged, this, &WndSets::saveHeaderState);
|
||||
}
|
||||
|
||||
void WndSets::closeEvent(QCloseEvent * /*ev*/)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent)
|
|||
resize(calculateDialogSize(hostSize, available.size(), minimum));
|
||||
setSizeGripEnabled(true);
|
||||
|
||||
setObjectName("settingsDialog");
|
||||
|
||||
connect(&SettingsCache::instance().personal(), &PersonalSettings::langChanged, this, &DlgSettings::updateLanguage);
|
||||
|
||||
settingsView = new SettingsView(this);
|
||||
|
|
@ -82,6 +84,7 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent)
|
|||
buttonBox->addStretch();
|
||||
okButton = new QPushButton;
|
||||
okButton->setDefault(true);
|
||||
okButton->setObjectName("settingsOkButton");
|
||||
connect(okButton, &QPushButton::clicked, this, &DlgSettings::close);
|
||||
buttonBox->addWidget(okButton);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
setObjectName("tipOfTheDayDialog");
|
||||
successfulInit = false;
|
||||
QString xmlPath = "theme:tips/tips_of_the_day.xml";
|
||||
tipDatabase = new TipsOfTheDay(xmlPath, this);
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ static const QString UPDATE_INSTALLER_OVERRIDE = "Cockatrice-Update-Override.exe
|
|||
|
||||
DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
setObjectName("updateDialog");
|
||||
|
||||
// Handle layout
|
||||
statusLabel = new QLabel(this);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class QWidget;
|
|||
|
||||
DlgViewLog::DlgViewLog(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
setObjectName("viewLogDialog");
|
||||
logArea = new QPlainTextEdit;
|
||||
logArea->setReadOnly(true);
|
||||
|
||||
|
|
|
|||
|
|
@ -119,6 +119,8 @@ QWidget *fromWidgetId(WidgetId id)
|
|||
|
||||
SettingsView::SettingsView(QWidget *parent) : QWidget(parent), currentTabIndex(0), searchActive(false)
|
||||
{
|
||||
setObjectName("settingsView");
|
||||
|
||||
setupUi();
|
||||
|
||||
connect(&SettingsCache::instance().personal(), &PersonalSettings::langChanged, this, &SettingsView::retranslateUi);
|
||||
|
|
@ -135,6 +137,7 @@ void SettingsView::setupUi()
|
|||
searchEdit->addAction(themePixmap(QStringLiteral("icons/search")), QLineEdit::LeadingPosition);
|
||||
searchEdit->installEventFilter(this);
|
||||
connect(searchEdit, &QLineEdit::textChanged, this, &SettingsView::onSearchTextChanged);
|
||||
searchEdit->setObjectName("settingsSearchEdit");
|
||||
|
||||
auto *searchLayout = new QHBoxLayout;
|
||||
searchLayout->addWidget(searchEdit);
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
|
||||
: QWidget(parent), tabSupervisor(_tabSupervisor), background(themePixmap(QStringLiteral("backgrounds/home")))
|
||||
{
|
||||
setObjectName("homeWidget");
|
||||
layout = new QGridLayout(this);
|
||||
|
||||
backgroundSourceCard = new CardInfoPictureArtCropWidget(this);
|
||||
|
|
@ -287,6 +288,7 @@ void HomeWidget::updateButtonsToBackgroundColor()
|
|||
QGroupBox *HomeWidget::createButtons()
|
||||
{
|
||||
QGroupBox *box = new QGroupBox(this);
|
||||
box->setObjectName("homeButtonsBox");
|
||||
box->setStyleSheet(R"(
|
||||
QGroupBox {
|
||||
font-size: 20px;
|
||||
|
|
@ -311,27 +313,34 @@ QGroupBox *HomeWidget::createButtons()
|
|||
boxLayout->addSpacing(25);
|
||||
|
||||
connectButton = new HomeStyledButton(QString(), gradientColors);
|
||||
connectButton->setObjectName("homeConnectButton");
|
||||
boxLayout->addWidget(connectButton);
|
||||
|
||||
visualDeckEditorButton = new HomeStyledButton(QString(), gradientColors);
|
||||
visualDeckEditorButton->setObjectName("homeCreateNewDeckButton");
|
||||
connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor,
|
||||
[this] { tabSupervisor->openDeckInNewTab(LoadedDeck()); });
|
||||
boxLayout->addWidget(visualDeckEditorButton);
|
||||
visualDeckStorageButton = new HomeStyledButton(QString(), gradientColors);
|
||||
visualDeckStorageButton->setObjectName("homeBrowseDecksButton");
|
||||
connect(visualDeckStorageButton, &QPushButton::clicked, tabSupervisor,
|
||||
[this] { tabSupervisor->actTabVisualDeckStorage(true); });
|
||||
boxLayout->addWidget(visualDeckStorageButton);
|
||||
visualDatabaseDisplayButton = new HomeStyledButton(QString(), gradientColors);
|
||||
visualDatabaseDisplayButton->setObjectName("homeBrowseCardDatabaseButton");
|
||||
connect(visualDatabaseDisplayButton, &QPushButton::clicked, tabSupervisor,
|
||||
&TabSupervisor::addVisualDatabaseDisplayTab);
|
||||
boxLayout->addWidget(visualDatabaseDisplayButton);
|
||||
edhrecButton = new HomeStyledButton(QString(), gradientColors);
|
||||
edhrecButton->setObjectName("homeBrowseEdhrecButton");
|
||||
connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab);
|
||||
boxLayout->addWidget(edhrecButton);
|
||||
archidektButton = new HomeStyledButton(QString(), gradientColors);
|
||||
archidektButton->setObjectName("homeBrowseArchidektButton");
|
||||
connect(archidektButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addArchidektTab);
|
||||
boxLayout->addWidget(archidektButton);
|
||||
replayButton = new HomeStyledButton(QString(), gradientColors);
|
||||
replayButton->setObjectName("homeViewReplaysButton");
|
||||
connect(replayButton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); });
|
||||
boxLayout->addWidget(replayButton);
|
||||
if (qobject_cast<MainWindow *>(tabSupervisor->parentWidget())) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ class TabSupervisor;
|
|||
|
||||
TabHome::TabHome(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
|
||||
{
|
||||
setObjectName("TabHome");
|
||||
homeWidget = new HomeWidget(this, tabSupervisor);
|
||||
setCentralWidget(homeWidget);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
#include <QPainter>
|
||||
#include <QRect>
|
||||
#include <QSharedPointer>
|
||||
#include <QStackedWidget>
|
||||
#include <QStringLiteral>
|
||||
#include <QStyle>
|
||||
#include <QStyleOption>
|
||||
|
|
@ -157,6 +158,15 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *
|
|||
tabReplays(nullptr), tabAdmin(nullptr), tabCardArtRules(nullptr), tabLog(nullptr), tabReport(nullptr),
|
||||
tabModeration(nullptr), tabDeveloper(nullptr), isLocalGame(false)
|
||||
{
|
||||
setObjectName("tabSupervisor");
|
||||
|
||||
// QTabWidget keeps tab pages in an internal stacked widget. Give it a stable
|
||||
// name so the GUI tests can address the pages by a readable path
|
||||
// (tabSupervisor/stackedWidget/<TabName>).
|
||||
if (auto *stackedWidget = findChild<QStackedWidget *>()) {
|
||||
stackedWidget->setObjectName("stackedWidget");
|
||||
}
|
||||
|
||||
setElideMode(Qt::ElideRight);
|
||||
setMovable(true);
|
||||
setIconSize(QSize(15, 15));
|
||||
|
|
@ -462,6 +472,7 @@ void TabSupervisor::addCloseButtonToTab(Tab *tab, int tabIndex, QAction *manager
|
|||
auto closeSide = static_cast<QTabBar::ButtonPosition>(
|
||||
tabBar()->style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, tabBar()));
|
||||
auto *closeButton = new CloseButton(tab);
|
||||
closeButton->setObjectName(QStringLiteral("tabCloseButton_%1").arg(tabIndex));
|
||||
if (manager) {
|
||||
// If managed, all close requests should go through the menu action
|
||||
connect(closeButton, &CloseButton::clicked, this, [manager] { checkAndTrigger(manager, false); });
|
||||
|
|
|
|||
|
|
@ -49,6 +49,13 @@
|
|||
#include "intents/url_parser.h"
|
||||
#include "latency_tracker.h"
|
||||
#include "logger.h"
|
||||
#ifdef WITH_GUI_TEST
|
||||
#include "pqEventObserver.h"
|
||||
#include "pqEventSource.h"
|
||||
#include "pqEventTypes.h"
|
||||
#include "pqTestUtility.h"
|
||||
#include "recording.h"
|
||||
#endif
|
||||
#include "pixel_map_generator.h"
|
||||
#include "version_string.h"
|
||||
#include "widgets/dialogs/dlg_connect.h"
|
||||
|
|
@ -306,6 +313,7 @@ void MainWindow::actAbout()
|
|||
mb.setIconPixmap(
|
||||
themePixmap(QStringLiteral("cockatrice")).scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
mb.setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
mb.setObjectName("aboutDialog");
|
||||
mb.exec();
|
||||
}
|
||||
|
||||
|
|
@ -344,6 +352,24 @@ void MainWindow::actOpenSettingsFolder()
|
|||
QtUtils::openPathInFileManager(dir);
|
||||
}
|
||||
|
||||
#ifdef WITH_GUI_TEST
|
||||
void MainWindow::actRecord()
|
||||
{
|
||||
QString filename = QFileDialog::getSaveFileName(this, tr("Test File Name"), QString(), tr("XML Files (*.xml)"));
|
||||
if (!filename.isEmpty()) {
|
||||
testUtility->recordTests(filename);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::actPlayRecording()
|
||||
{
|
||||
QString filename = QFileDialog::getOpenFileName(this, tr("Test File Name"), QString(), tr("XML Files (*.xml)"));
|
||||
if (!filename.isEmpty()) {
|
||||
testUtility->playTests(filename);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void MainWindow::retranslateUi()
|
||||
{
|
||||
connectionController->refreshWindowTitle();
|
||||
|
|
@ -388,6 +414,10 @@ void MainWindow::retranslateUi()
|
|||
aViewLog->setText(tr("View &Debug Log"));
|
||||
aOpenSettingsFolder->setText(tr("Open Settings Folder"));
|
||||
aFirstRunWizard->setText(tr("Re-run Onboarding Wizard..."));
|
||||
#ifdef WITH_GUI_TEST
|
||||
aRecord->setText(tr("&Record Test..."));
|
||||
aPlayRecording->setText(tr("&Play Test..."));
|
||||
#endif
|
||||
|
||||
aShow->setText(tr("Show/Hide"));
|
||||
|
||||
|
|
@ -397,24 +427,33 @@ void MainWindow::retranslateUi()
|
|||
void MainWindow::createActions()
|
||||
{
|
||||
aConnect = new QAction(this);
|
||||
aConnect->setObjectName("actionConnect");
|
||||
connect(aConnect, &QAction::triggered, this, &MainWindow::actConnect);
|
||||
aDisconnect = new QAction(this);
|
||||
aDisconnect->setObjectName("actionDisconnect");
|
||||
aDisconnect->setEnabled(false);
|
||||
connect(aDisconnect, &QAction::triggered, this, &MainWindow::actDisconnect);
|
||||
aSinglePlayer = new QAction(this);
|
||||
aSinglePlayer->setObjectName("actionSinglePlayer");
|
||||
connect(aSinglePlayer, &QAction::triggered, this, &MainWindow::actSinglePlayer);
|
||||
aWatchReplay = new QAction(this);
|
||||
aWatchReplay->setObjectName("actionWatchReplay");
|
||||
connect(aWatchReplay, &QAction::triggered, this, &MainWindow::actWatchReplay);
|
||||
aFullScreen = new QAction(this);
|
||||
aFullScreen->setObjectName("actionFullScreen");
|
||||
aFullScreen->setCheckable(true);
|
||||
connect(aFullScreen, &QAction::toggled, this, &MainWindow::actFullScreen);
|
||||
aRegister = new QAction(this);
|
||||
aRegister->setObjectName("actionRegister");
|
||||
connect(aRegister, &QAction::triggered, connectionController, &ConnectionController::registerToServer);
|
||||
aForgotPassword = new QAction(this);
|
||||
aForgotPassword->setObjectName("actionForgotPassword");
|
||||
connect(aForgotPassword, &QAction::triggered, connectionController, &ConnectionController::forgotPasswordRequest);
|
||||
aSettings = new QAction(this);
|
||||
aSettings->setObjectName("actionSettings");
|
||||
connect(aSettings, &QAction::triggered, this, &MainWindow::actSettings);
|
||||
aExit = new QAction(this);
|
||||
aExit->setObjectName("actionExit");
|
||||
connect(aExit, &QAction::triggered, this, &MainWindow::actExit);
|
||||
|
||||
aManageSets = new QAction(QString(), this);
|
||||
|
|
@ -451,6 +490,12 @@ void MainWindow::createActions()
|
|||
connect(aOpenSettingsFolder, &QAction::triggered, this, &MainWindow::actOpenSettingsFolder);
|
||||
aFirstRunWizard = new QAction(this);
|
||||
connect(aFirstRunWizard, &QAction::triggered, this, [this] { runFirstRunWizard(); });
|
||||
#ifdef WITH_GUI_TEST
|
||||
aRecord = new QAction(this);
|
||||
connect(aRecord, &QAction::triggered, this, &MainWindow::actRecord);
|
||||
aPlayRecording = new QAction(this);
|
||||
connect(aPlayRecording, &QAction::triggered, this, &MainWindow::actPlayRecording);
|
||||
#endif
|
||||
|
||||
aShow = new QAction(this);
|
||||
connect(aShow, &QAction::triggered, this, &MainWindow::actShow);
|
||||
|
|
@ -491,7 +536,9 @@ void MainWindow::createActions()
|
|||
|
||||
void MainWindow::createMenus()
|
||||
{
|
||||
menuBar()->setObjectName("menubar");
|
||||
cockatriceMenu = menuBar()->addMenu(QString());
|
||||
cockatriceMenu->setObjectName("cockatriceMenu");
|
||||
cockatriceMenu->addAction(aConnect);
|
||||
cockatriceMenu->addAction(aDisconnect);
|
||||
cockatriceMenu->addAction(aRegister);
|
||||
|
|
@ -507,6 +554,7 @@ void MainWindow::createMenus()
|
|||
cockatriceMenu->addAction(aExit);
|
||||
|
||||
dbMenu = menuBar()->addMenu(QString());
|
||||
dbMenu->setObjectName("dbMenu");
|
||||
dbMenu->addAction(aManageSets);
|
||||
dbMenu->addAction(aEditTokens);
|
||||
dbMenu->addSeparator();
|
||||
|
|
@ -517,8 +565,10 @@ void MainWindow::createMenus()
|
|||
dbMenu->addAction(aReloadCardDatabase);
|
||||
|
||||
tabsMenu = menuBar()->addMenu(QString());
|
||||
tabsMenu->setObjectName("tabsMenu");
|
||||
|
||||
helpMenu = menuBar()->addMenu(QString());
|
||||
helpMenu->setObjectName("helpMenu");
|
||||
helpMenu->addAction(aAbout);
|
||||
helpMenu->addAction(aTips);
|
||||
helpMenu->addSeparator();
|
||||
|
|
@ -531,12 +581,19 @@ void MainWindow::createMenus()
|
|||
helpMenu->addAction(aOpenSettingsFolder);
|
||||
helpMenu->addSeparator();
|
||||
helpMenu->addAction(aFirstRunWizard);
|
||||
#ifdef WITH_GUI_TEST
|
||||
helpMenu->addSeparator();
|
||||
helpMenu->addAction(aRecord);
|
||||
helpMenu->addAction(aPlayRecording);
|
||||
#endif
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent), localServer(nullptr), bHasActivated(false), askedForDbUpdater(false),
|
||||
: QMainWindow(parent), localServer(nullptr), bHasActivated(false), askedForDbUpdater(false), noConnect(false),
|
||||
cardUpdateProcess(nullptr), logviewDialog(nullptr)
|
||||
{
|
||||
setObjectName("MainWindow");
|
||||
|
||||
connect(&SettingsCache::instance().cacheStorage(), &CacheStorageSettings::pixmapCacheSizeChanged, this,
|
||||
&MainWindow::pixmapCacheSizeChanged);
|
||||
pixmapCacheSizeChanged(SettingsCache::instance().cacheStorage().getPixmapCacheSize());
|
||||
|
|
@ -622,6 +679,13 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
// run startup check async
|
||||
QTimer::singleShot(0, this, &MainWindow::startupConfigCheck);
|
||||
QTimer::singleShot(0, this, &MainWindow::applyStartupDestination);
|
||||
|
||||
#ifdef WITH_GUI_TEST
|
||||
testUtility = new pqTestUtility(this);
|
||||
testUtility->setRecordWithDialog(true);
|
||||
testUtility->addEventObserver("xml", new XMLEventObserver(this));
|
||||
testUtility->addEventSource("xml", new XMLEventSource(this));
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::startupConfigCheck()
|
||||
|
|
@ -776,6 +840,11 @@ void MainWindow::runFirstRunWizard(bool firstRun)
|
|||
*/
|
||||
void MainWindow::applyStartupDestination()
|
||||
{
|
||||
if (noConnect) {
|
||||
qCInfo(WindowMainStartupAutoconnectLog) << "Startup destination suppressed (--no-connect)";
|
||||
return;
|
||||
}
|
||||
|
||||
// An explicit command-line connect takes precedence over the startup destination.
|
||||
if (!connectTo.isEmpty()) {
|
||||
return;
|
||||
|
|
@ -1045,6 +1114,10 @@ void MainWindow::handleCockatriceLink(const QString &url)
|
|||
|
||||
void MainWindow::attemptStartupAutoConnect()
|
||||
{
|
||||
if (noConnect) {
|
||||
qCInfo(WindowMainStartupAutoconnectLog) << "Auto-connect suppressed (--no-connect)";
|
||||
return;
|
||||
}
|
||||
if (startupAutoConnectAttempted || skipStartupAutoConnect) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
class QAction;
|
||||
class QToolButton;
|
||||
class QWidget;
|
||||
class pqTestUtility;
|
||||
struct LocalGameOptions;
|
||||
|
||||
inline Q_LOGGING_CATEGORY(WindowMainLog, "window_main");
|
||||
|
|
@ -104,6 +105,11 @@ private slots:
|
|||
void actUpdate();
|
||||
void actViewLog();
|
||||
void actOpenSettingsFolder();
|
||||
#ifdef WITH_GUI_TEST
|
||||
void actRecord();
|
||||
void actPlayRecording();
|
||||
#endif
|
||||
|
||||
void actShow();
|
||||
void showWindowIfHidden();
|
||||
void onUrlChainFinished(bool connected);
|
||||
|
|
@ -185,6 +191,9 @@ private:
|
|||
*aReloadCardDatabase;
|
||||
QAction *aTips, *aUpdate, *aCheckCardUpdates, *aCheckCardUpdatesBackground, *aFirstRunWizard, *aStatusBar,
|
||||
*aViewLog, *aOpenSettingsFolder;
|
||||
#ifdef WITH_GUI_TEST
|
||||
QAction *aRecord, *aPlayRecording;
|
||||
#endif
|
||||
|
||||
TabSupervisor *tabSupervisor;
|
||||
IntentUrlParser *urlParser;
|
||||
|
|
@ -208,6 +217,7 @@ private:
|
|||
bool skipStartupAutoConnect = false;
|
||||
bool startupAutoConnectAttempted = false;
|
||||
bool firstRunWizardActive = false;
|
||||
bool noConnect = false; ///< --no-connect: skip every startup connection
|
||||
QProcess *cardUpdateProcess;
|
||||
QByteArray cardUpdateOutputBuffer;
|
||||
QByteArray cardUpdateErrorOutput; ///< diagnostic output collected from the updater's stderr
|
||||
|
|
@ -219,6 +229,9 @@ private:
|
|||
GameReplay *replay;
|
||||
DlgTipOfTheDay *tip;
|
||||
QUrl connectTo;
|
||||
#ifdef WITH_GUI_TEST
|
||||
pqTestUtility *testUtility;
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
|
|
@ -226,6 +239,16 @@ public:
|
|||
{
|
||||
connectTo = QUrl(QString("cockatrice://%1").arg(url));
|
||||
}
|
||||
/**
|
||||
* @brief Suppresses every startup connection: --connect, auto-connect and the
|
||||
* configured startup destination. Used by the GUI test harness so a
|
||||
* playback run never dials out.
|
||||
*/
|
||||
void setNoConnect(bool value)
|
||||
{
|
||||
noConnect = value;
|
||||
}
|
||||
|
||||
// When set, the window's own startup connection (--connect or auto-connect
|
||||
// on first activation) is skipped. Used for activation launches: the intent
|
||||
// chain triggered by a cockatrice:// URL owns the connection, and letting
|
||||
|
|
@ -266,6 +289,13 @@ public:
|
|||
*/
|
||||
void requestInstallerClose();
|
||||
|
||||
#ifdef WITH_GUI_TEST
|
||||
pqTestUtility *getTestUtility() const
|
||||
{
|
||||
return testUtility;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
void changeEvent(QEvent *event) override;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@
|
|||
#include "single_instance_manager.h"
|
||||
#include "version_string.h"
|
||||
|
||||
#ifdef WITH_GUI_TEST
|
||||
#include "pqTestUtility.h"
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include <QByteArray>
|
||||
#include <QByteArrayView>
|
||||
|
|
@ -62,10 +66,12 @@
|
|||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QStringLiteral>
|
||||
#include <QThread>
|
||||
#include <QTranslator>
|
||||
#include <QUrl>
|
||||
#include <QtGlobal>
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/rng/rng_sfmt.h>
|
||||
#include <libcockatrice/settings/card_database_settings.h>
|
||||
|
|
@ -343,9 +349,16 @@ int main(int argc, char *argv[])
|
|||
parser.addOptions(
|
||||
{{{"c", "connect"}, QCoreApplication::translate("main", "Connect on startup"), "user:pass@host:port"},
|
||||
{{"d", "debug-output"}, QCoreApplication::translate("main", "Debug to file")},
|
||||
{"simulate-crash",
|
||||
QCoreApplication::translate("main",
|
||||
"Show the crash-report dialog (saves a mock file when no DSN is configured)")}});
|
||||
{"simulate-crash", QCoreApplication::translate(
|
||||
"main", "Show the crash-report dialog (saves a mock file when no DSN is configured)")},
|
||||
{"no-connect", QCoreApplication::translate("main", "Suppress auto-connect on startup")}
|
||||
#ifdef WITH_GUI_TEST
|
||||
,
|
||||
{{"t", "test"},
|
||||
QCoreApplication::translate("main", "Play back a QtTesting XML test file and exit"),
|
||||
"test-file.xml"}
|
||||
#endif
|
||||
});
|
||||
|
||||
parser.process(app);
|
||||
|
||||
|
|
@ -483,6 +496,9 @@ int main(int argc, char *argv[])
|
|||
if (parser.isSet("connect")) {
|
||||
ui.setConnectTo(parser.value("connect"));
|
||||
}
|
||||
if (parser.isSet("no-connect")) {
|
||||
ui.setNoConnect(true);
|
||||
}
|
||||
qCInfo(MainLog) << "MainWindow constructor finished";
|
||||
|
||||
ui.setWindowIcon(themePixmap(QStringLiteral("cockatrice")));
|
||||
|
|
@ -523,6 +539,27 @@ int main(int argc, char *argv[])
|
|||
handleActivation(file);
|
||||
}
|
||||
|
||||
#ifdef WITH_GUI_TEST
|
||||
if (parser.isSet("test")) {
|
||||
QString testFile = parser.value("test");
|
||||
qCInfo(MainLog) << "Playing test file:" << testFile;
|
||||
QTimer::singleShot(0, &ui, [&ui, testFile]() {
|
||||
bool success = ui.getTestUtility()->playTests(testFile);
|
||||
qCInfo(MainLog) << "Test playback" << (success ? "succeeded" : "FAILED");
|
||||
qApp->exit(success ? 0 : 1);
|
||||
|
||||
// An event that fails while a modal dialog is open aborts the dispatcher,
|
||||
// but the dialog's nested event loop can then keep the application alive
|
||||
// forever. Hard-exit after a grace period so a red test always terminates.
|
||||
auto *watchdog = QThread::create([success] {
|
||||
QThread::sleep(10);
|
||||
::_Exit(success ? 0 : 1);
|
||||
});
|
||||
watchdog->start();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
// Connect to future file/URL events from other instances
|
||||
QObject::connect(&instance, &SingleInstanceManager::filesReceived, [&handleActivation](const QStringList &files) {
|
||||
for (const QString &file : files) {
|
||||
|
|
|
|||
1
external/qttesting
vendored
Submodule
1
external/qttesting
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c7bb5d372133e94c75cbdc70b04e039b864ea379
|
||||
Loading…
Add table
Add a link
Reference in a new issue