[Test] Add tests for widgets code

This creates a vbam-wx-widgets target for the custom widgets code and
adds tests for them.
This commit is contained in:
Fabrice de Gans 2024-05-13 14:14:38 -07:00 committed by Fabrice de Gans
parent 5766b9b9c7
commit 4f1a5dd726
31 changed files with 593 additions and 107 deletions

View File

@ -1,4 +1,4 @@
name: macOS Latest Build name: macOS Latest
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
@ -60,4 +60,4 @@ jobs:
- if: matrix.build_options == 'default' - if: matrix.build_options == 'default'
name: Run tests name: Run tests
run: >- run: >-
nix-shell --command 'cd build && ctest -j' nix-shell --command 'cd build && ctest -j --output-on-failure'

View File

@ -1,4 +1,4 @@
name: MSYS2 Build name: MSYS2
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:

View File

@ -1,4 +1,4 @@
name: Ubuntu Latest Build name: Ubuntu Latest
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
@ -45,6 +45,9 @@ jobs:
run: >- run: >-
bash installdeps; if [ "${{ matrix.build_compiler }}" = clang ]; then sudo apt -y install clang; fi bash installdeps; if [ "${{ matrix.build_compiler }}" = clang ]; then sudo apt -y install clang; fi
- name: Install xvfb
run: sudo apt -y install xvfb
# CMake build # CMake build
- if: matrix.build_options != 'libretro' - if: matrix.build_options != 'libretro'
name: Configure CMake name: Configure CMake
@ -65,4 +68,4 @@ jobs:
# Run tests # Run tests
- if: matrix.build_options == 'default' - if: matrix.build_options == 'default'
name: Run tests name: Run tests
run: cd build && ctest -j run: cd build && xvfb-run ctest -j --output-on-failure

View File

@ -1,4 +1,4 @@
name: Visual Studio Build name: Visual Studio
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
@ -57,4 +57,4 @@ jobs:
# Run tests # Run tests
- if: matrix.build_options == 'default' && matrix.msvc_arch != 'amd64_arm64' - if: matrix.build_options == 'default' && matrix.msvc_arch != 'amd64_arm64'
name: Run tests name: Run tests
run: cd build && ctest -j run: cd build && ctest -j --output-on-failure

View File

@ -51,6 +51,28 @@ if(NOT DEFINED VCPKG_TARGET_TRIPLET)
message(STATUS "Inferred VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}") message(STATUS "Inferred VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}")
endif() endif()
function(vcpkg_seconds)
if(CMAKE_HOST_SYSTEM MATCHES Windows OR ((NOT DEFINED CMAKE_HOST_SYSTEM) AND WIN32))
execute_process(
COMMAND cmd /c echo %TIME:~0,8%
OUTPUT_VARIABLE time
)
else()
execute_process(
COMMAND date +%H:%M:%S
OUTPUT_VARIABLE time
)
endif()
string(SUBSTRING "${time}" 0 2 hours)
string(SUBSTRING "${time}" 3 2 minutes)
string(SUBSTRING "${time}" 6 2 secs)
math(EXPR seconds "(${hours} * 60 * 60) + (${minutes} * 60) + ${secs}")
set(seconds ${seconds} PARENT_SCOPE)
endfunction()
function(vcpkg_check_git_status git_status) function(vcpkg_check_git_status git_status)
# The VS vcpkg component cannot be written to without elevation. # The VS vcpkg component cannot be written to without elevation.
if(NOT git_status EQUAL 0 AND NOT VCPKG_ROOT MATCHES "Visual Studio") if(NOT git_status EQUAL 0 AND NOT VCPKG_ROOT MATCHES "Visual Studio")

View File

@ -60,3 +60,5 @@ target_link_libraries(vbam-core-base
PRIVATE vbam-fex stb-image PRIVATE vbam-fex stb-image
PUBLIC ${ZLIB_LIBRARY} PUBLIC ${ZLIB_LIBRARY}
) )
add_subdirectory(test)

View File

@ -37,25 +37,21 @@
#define VBAM_CHECK(condition) \ #define VBAM_CHECK(condition) \
if (!(condition)) { \ if (!(condition)) { \
fputs("CHECK failed at " __FILE__ ":" VBAM_STRINGIFY(__LINE__) ": " #condition "\n", stderr); \ fputs("CHECK failed at " __FILE__ ":" VBAM_STRINGIFY(__LINE__) ": " #condition "\n", \
stderr); \
VBAM_IMMEDIATE_CRASH_DETAIL(); \ VBAM_IMMEDIATE_CRASH_DETAIL(); \
} \ } \
VBAM_REQUIRE_SEMICOLON_DETAIL() VBAM_REQUIRE_SEMICOLON_DETAIL()
#define VBAM_NOTREACHED_MESSAGE_DETAIL() \
fputs("NOTREACHED code reached at " __FILE__ ":" VBAM_STRINGIFY(__LINE__) "\n", stderr)
#if defined(DEBUG) #if defined(DEBUG)
#define VBAM_NOTREACHED() \ #define VBAM_NOTREACHED() \
VBAM_NOTREACHED_MESSAGE_DETAIL(); \ fputs("NOTREACHED code reached at " __FILE__ ":" VBAM_STRINGIFY(__LINE__) "\n", stderr); \
VBAM_IMMEDIATE_CRASH_DETAIL() VBAM_IMMEDIATE_CRASH_DETAIL()
#else // defined(DEBUG) #else // defined(DEBUG)
#define VBAM_NOTREACHED() \ #define VBAM_NOTREACHED() VBAM_INTRINSIC_UNREACHABLE_DETAIL()
VBAM_NOTREACHED_MESSAGE_DETAIL(); \
VBAM_INTRINSIC_UNREACHABLE_DETAIL()
#endif // defined(DEBUG) #endif // defined(DEBUG)

View File

@ -0,0 +1,12 @@
# This defines the `vbam-core-base-test` library.
if(NOT BUILD_TESTING)
return()
endif()
add_library(vbam-core-base-test
INTERFACE
notreached.h)
target_link_libraries(vbam-core-base-test
INTERFACE GTest::gtest)

View File

@ -0,0 +1,22 @@
#ifndef VBAM_CORE_BASE_TEST_NOTREACHED_H_
#define VBAM_CORE_BASE_TEST_NOTREACHED_H_
#include <gtest/gtest.h>
#if defined(DEBUG)
#define VBAM_EXPECT_NOTREACHED(statement) EXPECT_DEATH(statement, "NOTREACHED")
#else // defined(DEBUG)
// There is no way to test this in release builds as the compiler might optimize
// this code path away. Eat the statement to avoid unused variable warnings.
#define VBAM_EXPECT_NOTREACHED(statement) \
if constexpr (false) { \
statement; \
} \
static_assert(true, "")
#endif // defined(DEBUG)
#endif // VBAM_CORE_BASE_TEST_NOTREACHED_H_

View File

@ -46,33 +46,6 @@ set(VBAM_WX_COMMON
viewsupt.h viewsupt.h
wayland.cpp wayland.cpp
wayland.h wayland.h
# from external source with minor modifications
widgets/checkedlistctrl.cpp
widgets/checkedlistctrl.h
widgets/client-data.h
widgets/dpi-support.h
widgets/event-handler-provider.h
widgets/group-check-box.cpp
widgets/group-check-box.h
widgets/keep-on-top-styler.cpp
widgets/keep-on-top-styler.h
widgets/option-validator.cpp
widgets/option-validator.h
widgets/render-plugin.cpp
widgets/render-plugin.h
widgets/user-input-ctrl.cpp
widgets/user-input-ctrl.h
widgets/user-input-event.cpp
widgets/user-input-event.h
widgets/sdl-poller.cpp
widgets/sdl-poller.h
widgets/shortcut-menu-bar.cpp
widgets/shortcut-menu-bar.h
widgets/utils.cpp
widgets/utils.h
widgets/webupdatedef.h
widgets/wxmisc.h
widgets/wxmisc.cpp
wxhead.h wxhead.h
wxlogdebug.h wxlogdebug.h
wxvbam.cpp wxvbam.cpp
@ -253,6 +226,9 @@ function(configure_wx_target target)
endif() endif()
endfunction() endfunction()
# Core emulator.
_add_link_libraries(vbam-core)
# Nonstd. # Nonstd.
_add_link_libraries(nonstd-lib) _add_link_libraries(nonstd-lib)
_add_include_directories(${NONSTD_INCLUDE_DIR}) _add_include_directories(${NONSTD_INCLUDE_DIR})
@ -311,6 +287,7 @@ endfunction()
# Sub-projects. # Sub-projects.
add_subdirectory(test) add_subdirectory(test)
add_subdirectory(config) add_subdirectory(config)
add_subdirectory(widgets)
set(VBAM_ICON visualboyadvance-m.icns) set(VBAM_ICON visualboyadvance-m.icns)
set(VBAM_ICON_PATH ${CMAKE_CURRENT_SOURCE_DIR}/icons/${VBAM_ICON}) set(VBAM_ICON_PATH ${CMAKE_CURRENT_SOURCE_DIR}/icons/${VBAM_ICON})
@ -326,13 +303,13 @@ target_include_directories(visualboyadvance-m PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries( target_link_libraries(
visualboyadvance-m visualboyadvance-m
vbam-core
vbam-components-draw-text vbam-components-draw-text
vbam-components-filters vbam-components-filters
vbam-components-filters-agb vbam-components-filters-agb
vbam-components-filters-interframe vbam-components-filters-interframe
vbam-components-user-config vbam-components-user-config
vbam-wx-config vbam-wx-config
vbam-wx-widgets
) )
# adjust link command when making a static binary for gcc # adjust link command when making a static binary for gcc

View File

@ -61,12 +61,6 @@ target_sources(vbam-wx-config
user-input.h user-input.h
) )
target_link_libraries(vbam-wx-config
PUBLIC
nonstd-lib
vbam-core
)
configure_wx_target(vbam-wx-config) configure_wx_target(vbam-wx-config)
if(BUILD_TESTING) if(BUILD_TESTING)
@ -79,10 +73,12 @@ if (BUILD_TESTING)
user-input-test.cpp user-input-test.cpp
) )
target_link_libraries(vbam-wx-config-tests target_link_libraries(vbam-wx-config-tests
vbam-core # Test deps.
vbam-core-fake vbam-core-fake
vbam-wx-config
vbam-wx-fake-opts vbam-wx-fake-opts
# Target deps.
vbam-wx-config
GTest::gtest_main GTest::gtest_main
) )

View File

@ -135,7 +135,8 @@ TEST_F(GamepadTest, NonDefaultInput) {
const config::KeyboardInput f1(WXK_F1); const config::KeyboardInput f1(WXK_F1);
// Assign F1 to "Up". // Assign F1 to "Up".
bindings()->AssignInputToCommand(f1, config::GameCommand(config::GameJoy(0), config::GameKey::Up)); bindings()->AssignInputToCommand(f1,
config::GameCommand(config::GameJoy(0), config::GameKey::Up));
// Press F1, the up key should be pressed. // Press F1, the up key should be pressed.
EXPECT_TRUE(gamepad()->OnInputPressed(f1)); EXPECT_TRUE(gamepad()->OnInputPressed(f1));

View File

@ -3,6 +3,8 @@
#include "core/base/check.h" #include "core/base/check.h"
#include "wx/config/option.h" #include "wx/config/option.h"
#include "core/base/check.h"
namespace config { namespace config {
// An Option::Observer that calls a callback when an option has changed. // An Option::Observer that calls a callback when an option has changed.

View File

@ -6,10 +6,8 @@ if(NOT BUILD_TESTING)
endif() endif()
add_library(vbam-wx-fake-opts OBJECT) add_library(vbam-wx-fake-opts OBJECT)
target_sources(vbam-wx-fake-opts target_sources(vbam-wx-fake-opts
PRIVATE PRIVATE
fake_opts.cpp fake-opts.cpp
) )
configure_wx_target(vbam-wx-fake-opts) configure_wx_target(vbam-wx-fake-opts)

View File

@ -0,0 +1,70 @@
# This defines the vbam-wx-widgets target.
# This does not export any localizable file as there is no localized string
# in this target.
add_library(vbam-wx-widgets OBJECT)
target_sources(vbam-wx-widgets
PRIVATE
# from external source with minor modifications
checkedlistctrl.cpp
group-check-box.cpp
keep-on-top-styler.cpp
option-validator.cpp
render-plugin.cpp
user-input-ctrl.cpp
user-input-event.cpp
sdl-poller.cpp
shortcut-menu-bar.cpp
utils.cpp
wxmisc.cpp
PUBLIC
# from external source with minor modifications
checkedlistctrl.h
client-data.h
dpi-support.h
event-handler-provider.h
group-check-box.h
keep-on-top-styler.h
option-validator.h
render-plugin.h
user-input-ctrl.h
user-input-event.h
sdl-poller.h
shortcut-menu-bar.h
utils.h
wxmisc.h
)
target_link_libraries(vbam-wx-widgets PUBLIC vbam-wx-config)
configure_wx_target(vbam-wx-widgets)
if(BUILD_TESTING)
add_executable(vbam-wx-widgets-tests
client-data-test.cpp
group-check-box-test.cpp
keep-on-top-styler-test.cpp
option-validator-test.cpp
)
target_link_libraries(vbam-wx-widgets-tests
# Test deps.
vbam-core-base-test
vbam-core-fake
vbam-wx-fake-opts
vbam-wx-widgets-test-fixture
# Target deps.
vbam-wx-config
vbam-wx-widgets
GTest::gtest_main
)
configure_wx_target(vbam-wx-widgets-tests)
if (NOT CMAKE_CROSSCOMPILING)
gtest_discover_tests(vbam-wx-widgets-tests)
endif()
endif()
add_subdirectory(test)

View File

@ -13,10 +13,21 @@
#define VBAM_WX_WIDGETS_CHECKEDLISTCTRL_H_ #define VBAM_WX_WIDGETS_CHECKEDLISTCTRL_H_
// wxWidgets headers // wxWidgets headers
#include "wx/widgets/webupdatedef.h" // for the WXDLLIMPEXP_WEBUPDATE macro
#include <wx/imaglist.h> #include <wx/imaglist.h>
#include <wx/listctrl.h> #include <wx/listctrl.h>
// this will never be part of a DLL
#define WXDLLIMPEXP_WEBUPDATE
// why would I include it and not enable it?
#define wxUSE_CHECKEDLISTCTRL 1
// enable customizations:
// - include wx/settings.h (bugfix; always enabled)
// - make a dynamic class so it can be subclass in xrc
// - only make col0 checkable
// - use "native" checkbox (also requires CLC_USE_SYSICONS)
#define CLC_VBAM_USAGE 1
#define CLC_USE_SYSICONS 1
#if wxUSE_CHECKEDLISTCTRL #if wxUSE_CHECKEDLISTCTRL
// image indexes (used internally by wxCheckedListCtrl) // image indexes (used internally by wxCheckedListCtrl)

View File

@ -0,0 +1,30 @@
#include "wx/widgets/client-data.h"
#include <memory>
#include <gtest/gtest.h>
#include <wx/window.h>
#include "wx/config/user-input.h"
namespace widgets {
// Test the ClientData class with a simple type.
TEST(ClientDataTest, Int) {
std::unique_ptr<wxWindow> window(std::make_unique<wxWindow>());
window->SetClientObject(new ClientData<int>(42));
EXPECT_EQ(ClientData<int>::From(window.get()), 42);
}
// Test the ClientData class with a complex type.
TEST(ClientDataTest, Object) {
std::unique_ptr<wxWindow> window(std::make_unique<wxWindow>());
window->SetClientObject(new ClientData<config::UserInput>(config::KeyboardInput('4')));
EXPECT_EQ(ClientData<config::UserInput>::From(window.get()).device(),
config::UserInput::Device::Keyboard);
}
} // namespace widgets

View File

@ -0,0 +1,27 @@
#include "wx/widgets/group-check-box.h"
#include <gtest/gtest.h>
#include <wx/frame.h>
#include <wx/sizer.h>
#include <wx/xrc/xmlres.h>
#include "wx/widgets/test/widgets-test.h"
namespace widgets {
TEST_F(WidgetsTest, GroupCheckBoxTest) {
// Add 2 GroupCheckBoxes to the frame, `frame()` takes ownership here.
GroupCheckBox* check_box_1 = new GroupCheckBox(frame(), XRCID("One"), "One");
GroupCheckBox* check_box_2 = new GroupCheckBox(frame(), XRCID("Two"), "Two");
// Tick one checkbox and check the other is unticked.
check_box_1->SetValue(true);
EXPECT_FALSE(check_box_2->GetValue());
// And vice-versa.
check_box_2->SetValue(true);
EXPECT_FALSE(check_box_1->GetValue());
}
} // namespace widgets

View File

@ -0,0 +1,37 @@
#include "wx/widgets/keep-on-top-styler.h"
#include <gtest/gtest.h>
#include <wx/frame.h>
#include <wx/xrc/xmlres.h>
#include "wx/config/option-proxy.h"
#include "wx/widgets/test/widgets-test.h"
namespace widgets {
TEST_F(WidgetsTest, KeepOnTopStyler) {
// Enable the keep on top option.
OPTION(kDispKeepOnTop) = true;
// Create a Frame, it should not have the wxSTAY_ON_TOP style.
EXPECT_EQ(frame()->GetWindowStyle() & wxSTAY_ON_TOP, 0);
// Add a KeepOnTopStyler to the frame.
KeepOnTopStyler styler(frame());
// Send the Show event to the frame.
wxShowEvent show_event(wxEVT_SHOW, true);
frame()->GetEventHandler()->ProcessEvent(show_event);
// The frame should have the wxSTAY_ON_TOP style.
EXPECT_EQ(frame()->GetWindowStyle() & wxSTAY_ON_TOP, wxSTAY_ON_TOP);
// Disable the keep on top option.
OPTION(kDispKeepOnTop) = false;
// The frame should no longer have the wxSTAY_ON_TOP style.
EXPECT_EQ(frame()->GetWindowStyle() & wxSTAY_ON_TOP, 0);
}
} // namespace widgets

View File

@ -0,0 +1,156 @@
#include "wx/widgets/option-validator.h"
#include <gtest/gtest.h>
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/frame.h>
#include <wx/spinctrl.h>
#include <wx/window.h>
#include <wx/xrc/xmlres.h>
#include "core/base/test/notreached.h"
#include "wx/config/option.h"
#include "wx/widgets/test/widgets-test.h"
namespace widgets {
TEST_F(WidgetsTest, OptionSelectedValidator) {
wxCheckBox* check_box_0 = new wxCheckBox(frame(), XRCID("Zero"), "Zero");
wxCheckBox* check_box_1 = new wxCheckBox(frame(), XRCID("One"), "One");
config::Option* option = config::Option::ByID(config::OptionID::kPrefCaptureFormat);
check_box_0->SetValidator(OptionSelectedValidator(config::OptionID::kPrefCaptureFormat, 0));
check_box_1->SetValidator(OptionSelectedValidator(config::OptionID::kPrefCaptureFormat, 1));
// Tick the first checkbox, the second should be unticked.
check_box_0->SetValue(true);
frame()->TransferDataFromWindow();
EXPECT_EQ(option->GetUnsigned(), 0);
EXPECT_FALSE(check_box_1->GetValue());
// And vice-versa.
check_box_1->SetValue(true);
frame()->TransferDataFromWindow();
EXPECT_EQ(option->GetUnsigned(), 1);
EXPECT_FALSE(check_box_0->GetValue());
// Set the kPrefCaptureFormat option to 0, the first checkbox should be ticked.
option->SetUnsigned(0);
frame()->TransferDataToWindow();
EXPECT_TRUE(check_box_0->GetValue());
EXPECT_FALSE(check_box_1->GetValue());
// Set the kPrefCaptureFormat option to 1, the second checkbox should be ticked.
option->SetUnsigned(1);
frame()->TransferDataToWindow();
EXPECT_FALSE(check_box_0->GetValue());
EXPECT_TRUE(check_box_1->GetValue());
}
TEST_F(WidgetsTest, OptionSelectedValidator_InvalidData) {
wxCheckBox* check_box = new wxCheckBox(frame(), XRCID("Checbox"), "Checkbox");
// The value is higher than the max for this option.
EXPECT_DEATH(
check_box->SetValidator(OptionSelectedValidator(config::OptionID::kPrefCaptureFormat, 2)),
"value_ <= option\\(\\)->GetUnsignedMax\\(\\)");
// The option is not an unsigned option.
EXPECT_DEATH(check_box->SetValidator(OptionSelectedValidator(config::OptionID::kDispFilter, 0)),
"option\\(\\)->is_unsigned\\(\\)");
// Unsupported wxWindow type.
wxWindow* window = new wxWindow(frame(), XRCID("Window"));
#if WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
VBAM_EXPECT_NOTREACHED(
window->SetValidator(OptionSelectedValidator(config::OptionID::kPrefCaptureFormat, 0)));
#else // WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
window->SetValidator(OptionSelectedValidator(config::OptionID::kPrefCaptureFormat, 0));
VBAM_EXPECT_NOTREACHED(frame()->TransferDataToWindow());
#endif // WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
}
TEST_F(WidgetsTest, OptionIntValidator) {
wxSpinCtrl* spin_ctrl = new wxSpinCtrl(frame(), XRCID("SpinCtrl"));
config::Option* option = config::Option::ByID(config::OptionID::kDispMaxThreads);
spin_ctrl->SetValidator(OptionIntValidator(config::OptionID::kDispMaxThreads));
// Set the kPrefCaptureFormat option to 0, the spin control should be set to 0.
option->SetInt(0);
frame()->TransferDataToWindow();
EXPECT_EQ(spin_ctrl->GetValue(), 0);
// Set the spin control to 100, the kPrefCaptureFormat option should be set to 100.
spin_ctrl->SetValue(100);
frame()->TransferDataFromWindow();
EXPECT_EQ(option->GetInt(), 100);
}
TEST_F(WidgetsTest, OptionIntValidator_InvalidData) {
wxSpinCtrl* spin_ctrl = new wxSpinCtrl(frame(), XRCID("SpinCtrl"));
// The option is not an int option.
EXPECT_DEATH(spin_ctrl->SetValidator(OptionIntValidator(config::OptionID::kDispFilter)),
"option\\(\\)->is_int\\(\\)");
// Unsupported wxWindow type.
wxWindow* window = new wxWindow(frame(), XRCID("Window"));
#if WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
VBAM_EXPECT_NOTREACHED(
window->SetValidator(OptionIntValidator(config::OptionID::kDispMaxThreads)));
#else // WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
window->SetValidator(OptionIntValidator(config::OptionID::kDispMaxThreads));
VBAM_EXPECT_NOTREACHED(frame()->TransferDataToWindow());
#endif // WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
}
TEST_F(WidgetsTest, OptionChoiceValidator) {
wxChoice* choice = new wxChoice(frame(), XRCID("Choice"));
choice->Append("Zero");
choice->Append("One");
config::Option* option = config::Option::ByID(config::OptionID::kPrefCaptureFormat);
choice->SetValidator(OptionChoiceValidator(config::OptionID::kPrefCaptureFormat));
// Set the kPrefCaptureFormat option to 0, the first item should be selected.
option->SetUnsigned(0);
frame()->TransferDataToWindow();
EXPECT_EQ(choice->GetSelection(), 0);
// Set the kPrefCaptureFormat option to 1, the second item should be selected.
option->SetUnsigned(1);
frame()->TransferDataToWindow();
EXPECT_EQ(choice->GetSelection(), 1);
// Select the first item, the kPrefCaptureFormat option should be 0.
choice->SetSelection(0);
frame()->TransferDataFromWindow();
EXPECT_EQ(option->GetUnsigned(), 0);
// Select the second item, the kPrefCaptureFormat option should be 1.
choice->SetSelection(1);
frame()->TransferDataFromWindow();
EXPECT_EQ(option->GetUnsigned(), 1);
}
TEST_F(WidgetsTest, OptionChoiceValidator_InvalidData) {
wxChoice* choice = new wxChoice(frame(), XRCID("Choice"));
// The option is not an unsigned option.
EXPECT_DEATH(choice->SetValidator(OptionChoiceValidator(config::OptionID::kDispFilter)),
"option\\(\\)->is_unsigned\\(\\)");
// Unsupported wxWindow type.
wxWindow* window = new wxWindow(frame(), XRCID("Window"));
#if WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
VBAM_EXPECT_NOTREACHED(
window->SetValidator(OptionChoiceValidator(config::OptionID::kPrefCaptureFormat)));
#else // WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
window->SetValidator(OptionChoiceValidator(config::OptionID::kPrefCaptureFormat));
VBAM_EXPECT_NOTREACHED(frame()->TransferDataToWindow());
#endif // WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
}
} // namespace widgets

View File

@ -28,18 +28,15 @@ bool OptionValidator::TransferToWindow() {
#if WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE #if WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
void OptionValidator::SetWindow(wxWindow* window) { void OptionValidator::SetWindow(wxWindow* window) {
wxValidator::SetWindow(window); wxValidator::SetWindow(window);
[[maybe_unused]] const bool write_success = WriteToWindow(); OnValueChanged();
VBAM_CHECK(write_success);
} }
#endif #endif
void OptionValidator::OnValueChanged() { void OptionValidator::OnValueChanged() {
[[maybe_unused]] const bool write_success = WriteToWindow(); VBAM_CHECK(WriteToWindow());
VBAM_CHECK(write_success);
} }
OptionSelectedValidator::OptionSelectedValidator(config::OptionID option_id, OptionSelectedValidator::OptionSelectedValidator(config::OptionID option_id, uint32_t value)
uint32_t value)
: OptionValidator(option_id), value_(value) { : OptionValidator(option_id), value_(value) {
VBAM_CHECK(option()->is_unsigned()); VBAM_CHECK(option()->is_unsigned());
VBAM_CHECK(value_ >= option()->GetUnsignedMin()); VBAM_CHECK(value_ >= option()->GetUnsignedMin());
@ -80,8 +77,7 @@ bool OptionSelectedValidator::WriteToOption() {
return true; return true;
} }
const wxRadioButton* radio_button = const wxRadioButton* radio_button = wxDynamicCast(GetWindow(), wxRadioButton);
wxDynamicCast(GetWindow(), wxRadioButton);
if (radio_button) { if (radio_button) {
if (radio_button->GetValue()) { if (radio_button->GetValue()) {
option()->SetUnsigned(value_); option()->SetUnsigned(value_);
@ -93,8 +89,7 @@ bool OptionSelectedValidator::WriteToOption() {
return false; return false;
} }
OptionIntValidator::OptionIntValidator(config::OptionID option_id) OptionIntValidator::OptionIntValidator(config::OptionID option_id) : OptionValidator(option_id) {
: OptionValidator(option_id) {
VBAM_CHECK(option()->is_int()); VBAM_CHECK(option()->is_int());
} }
@ -153,19 +148,26 @@ bool OptionChoiceValidator::IsWindowValueValid() {
bool OptionChoiceValidator::WriteToWindow() { bool OptionChoiceValidator::WriteToWindow() {
wxChoice* choice = wxDynamicCast(GetWindow(), wxChoice); wxChoice* choice = wxDynamicCast(GetWindow(), wxChoice);
VBAM_CHECK(choice); if (choice) {
choice->SetSelection(option()->GetUnsigned()); choice->SetSelection(option()->GetUnsigned());
return true; return true;
} }
VBAM_NOTREACHED();
return false;
}
bool OptionChoiceValidator::WriteToOption() { bool OptionChoiceValidator::WriteToOption() {
const wxChoice* choice = wxDynamicCast(GetWindow(), wxChoice); const wxChoice* choice = wxDynamicCast(GetWindow(), wxChoice);
VBAM_CHECK(choice); if (choice) {
return option()->SetUnsigned(choice->GetSelection()); return option()->SetUnsigned(choice->GetSelection());
} }
OptionBoolValidator::OptionBoolValidator(config::OptionID option_id) VBAM_NOTREACHED();
: OptionValidator(option_id) { return false;
}
OptionBoolValidator::OptionBoolValidator(config::OptionID option_id) : OptionValidator(option_id) {
VBAM_CHECK(option()->is_bool()); VBAM_CHECK(option()->is_bool());
} }
@ -179,15 +181,23 @@ bool OptionBoolValidator::IsWindowValueValid() {
bool OptionBoolValidator::WriteToWindow() { bool OptionBoolValidator::WriteToWindow() {
wxCheckBox* checkbox = wxDynamicCast(GetWindow(), wxCheckBox); wxCheckBox* checkbox = wxDynamicCast(GetWindow(), wxCheckBox);
VBAM_CHECK(checkbox); if (checkbox) {
checkbox->SetValue(option()->GetBool()); checkbox->SetValue(option()->GetBool());
return true; return true;
} }
VBAM_NOTREACHED();
return false;
}
bool OptionBoolValidator::WriteToOption() { bool OptionBoolValidator::WriteToOption() {
const wxCheckBox* checkbox = wxDynamicCast(GetWindow(), wxCheckBox); const wxCheckBox* checkbox = wxDynamicCast(GetWindow(), wxCheckBox);
VBAM_CHECK(checkbox); if (checkbox) {
return option()->SetBool(checkbox->GetValue()); return option()->SetBool(checkbox->GetValue());
} }
VBAM_NOTREACHED();
return false;
}
} // namespace widgets } // namespace widgets

View File

@ -85,7 +85,7 @@ private:
void OnValueChanged() final; void OnValueChanged() final;
}; };
// "Generic" validator for a wxChecBox or wxRadioButton widget with a kUnsigned // "Generic" validator for a wxCheckBox or wxRadioButton widget with a kUnsigned
// Option. This will make sure the kUnsigned Option and the wxRadioButton or // Option. This will make sure the kUnsigned Option and the wxRadioButton or
// wxCheckBox are kept in sync. The widget will be checked if the kUnsigned // wxCheckBox are kept in sync. The widget will be checked if the kUnsigned
// Option matches the provided `value` parameter in the constructor. // Option matches the provided `value` parameter in the constructor.

View File

@ -36,10 +36,10 @@ public:
private: private:
// Helper method to find a joystick state from a joystick ID. // Helper method to find a joystick state from a joystick ID.
// Returns nullptr if not present. Called from the SDL worker thread. // Returns nullptr if not present.
JoyState* FindJoyState(const SDL_JoystickID& joy_id); JoyState* FindJoyState(const SDL_JoystickID& joy_id);
// Remap all controllers. Called from the SDL worker thread. // Remap all controllers.
void RemapControllers(); void RemapControllers();
// Reconnects all controllers. // Reconnects all controllers.

View File

@ -0,0 +1,19 @@
# This defines the `vbam-wx-widgets-test-fixture` library, which is used for
# providing a base fixture for tests using wxWidgets.
if(NOT BUILD_TESTING)
return()
endif()
add_library(vbam-wx-widgets-test-fixture OBJECT)
target_sources(vbam-wx-widgets-test-fixture
PRIVATE
test-app.cpp
widgets-test.cpp
PUBLIC
test-app.h
widgets-test.h
)
configure_wx_target(vbam-wx-widgets-test-fixture)
target_link_libraries(vbam-wx-widgets-test-fixture PUBLIC GTest::gtest)

View File

@ -0,0 +1,22 @@
#include "wx/widgets/test/test-app.h"
namespace widgets {
TestApp::TestApp() : previous_app_(wxApp::GetInstance()) {
// Initialize the wxWidgets app.
int argc = 0;
wxApp::Initialize(argc, nullptr);
// Set the wxApp instance to this object.
wxApp::SetInstance(this);
}
TestApp::~TestApp() {
// Clean up the wxWidgets app.
wxApp::CleanUp();
// Restore the previous wxApp instance.
wxApp::SetInstance(previous_app_);
}
} // namespace widgets

View File

@ -0,0 +1,23 @@
#ifndef VBAM_WX_WIDGETS_TEST_TEST_APP_H_
#define VBAM_WX_WIDGETS_TEST_TEST_APP_H_
#include <wx/app.h>
namespace widgets {
// While this object is in scope, wxApp::GetInstance() will return this object.
class TestApp : public wxApp {
public:
TestApp();
~TestApp() override;
bool OnInit() override { return true; }
private:
// The previous wxApp instance, active before we set this one.
wxAppConsole* const previous_app_;
};
} // namespace widgets
#endif // VBAM_WX_WIDGETS_TEST_TEST_APP_H_

View File

@ -0,0 +1,29 @@
#include "wx/widgets/test/widgets-test.h"
#include <wx/xrc/xmlres.h>
namespace widgets {
WidgetsTest::WidgetsTest() {
// wxWidgets can run multi-threaded so set this up for death tests.
GTEST_FLAG_SET(death_test_style, "threadsafe");
}
WidgetsTest::~WidgetsTest() = default;
void WidgetsTest::SetUp() {
// Give the wxFrame a unique name and initialize it.
const char* test_name(testing::UnitTest::GetInstance()->current_test_info()->name());
frame_ = std::make_unique<wxFrame>(nullptr, wxXmlResource ::DoGetXRCID(test_name), test_name);
}
void WidgetsTest::TearDown() {
ASSERT_FALSE(frame_->IsShown());
ASSERT_FALSE(frame_->IsBeingDeleted());
// Ensure the frame is destroyed before the app.
frame_->Destroy();
frame_.reset();
}
} // namespace widgets

View File

@ -0,0 +1,35 @@
#ifndef VBAM_WX_WIDGETS_TEST_WIDGETS_TEST_H_
#define VBAM_WX_WIDGETS_TEST_WIDGETS_TEST_H_
#include <memory>
#include <gtest/gtest.h>
#include <wx/frame.h>
#include "wx/widgets/test/test-app.h"
namespace widgets {
// A base fixture for tests that use wxWidgets widgets. An app and a wxFrame are
// provided for convenience. They will be deleted on test teardown.
class WidgetsTest : public testing::Test {
public:
WidgetsTest();
~WidgetsTest() override;
// testing::Test implementation.
void SetUp() override;
void TearDown() override;
protected:
wxFrame* frame() { return frame_.get(); }
private:
TestApp test_app_;
std::unique_ptr<wxFrame> frame_;
};
} // namespace widgets
#endif // VBAM_WX_WIDGETS_TEST_WIDGETS_TEST_H_

View File

@ -39,7 +39,7 @@ public:
// Returns the first pressed input, if any. // Returns the first pressed input, if any.
nonstd::optional<config::UserInput> FirstReleasedInput() const; nonstd::optional<config::UserInput> FirstReleasedInput() const;
// Mark `event_data` as processed and returns the new event filter. This is // Marks `user_input` as processed and returns the new event filter. This is
// meant to be used with FilterEvent() to process global shortcuts before // meant to be used with FilterEvent() to process global shortcuts before
// sending the event to the next handler. // sending the event to the next handler.
int FilterProcessedInput(const config::UserInput& user_input); int FilterProcessedInput(const config::UserInput& user_input);

View File

@ -1,14 +0,0 @@
// dummy webupdatedef.h for checklistctrl
#ifndef WXDLLIMPEXP_WEBUPDATE
// this will never be part of a DLL
#define WXDLLIMPEXP_WEBUPDATE
// why would I include it and not enable it?
#define wxUSE_CHECKEDLISTCTRL 1
// enable customizations:
// - include wx/settings.h (bugfix; always enabled)
// - make a dynamic class so it can be subclass in xrc
// - only make col0 checkable
// - use "native" checkbox (also requires CLC_USE_SYSICONS)
#define CLC_VBAM_USAGE 1
#define CLC_USE_SYSICONS 1
#endif