175 lines
9.0 KiB
C++
175 lines
9.0 KiB
C++
#include <boost/test/unit_test.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <string>
|
|
|
|
#include <vamp_pico/http_client.hpp>
|
|
#include <vamp_zero/control.hpp>
|
|
#include <vamp_zero/library.hpp>
|
|
#include <vamp_zero/menu_view.hpp>
|
|
#include <vamp_zero/page_control.hpp>
|
|
|
|
|
|
namespace {
|
|
|
|
constexpr auto menu_bounds = luma::rect{0.0F, 80.0F, 1024.0F, 520.0F};
|
|
|
|
auto action(vamp_zero::app_state const &state, std::size_t row, vamp_zero::menu_action_kind kind = vamp_zero::menu_action_kind::ACTIVATE) -> vamp_zero::menu_action {
|
|
return {kind, row, state.input_revision, state.catalog};
|
|
}
|
|
|
|
auto point_event(luma::touch_phase phase, luma::point point, std::chrono::steady_clock::time_point time, std::int32_t id = 1) -> luma::touch_event {
|
|
return {.points = {{.id = id, .position = point, .phase = phase}}, .time = time};
|
|
}
|
|
|
|
auto row_center(vamp_zero::app_state const &state, std::size_t row, vamp_zero::menu_action_kind kind = vamp_zero::menu_action_kind::ACTIVATE) -> luma::point {
|
|
auto targets = vamp_zero::menu_layout(menu_bounds, state);
|
|
auto found = std::find_if(targets.begin(), targets.end(), [&](auto const &target) { return target.action.row == row && target.action.kind == kind; });
|
|
BOOST_REQUIRE(found != targets.end());
|
|
return {found->bounds.x + found->bounds.width / 2.0F, found->bounds.y + found->bounds.height / 2.0F};
|
|
}
|
|
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(menu_actions_share_navigation_and_preserve_settings_focus) {
|
|
auto state = vamp_zero::app_state{};
|
|
BOOST_TEST(vamp_zero::menu_count(state) == 2U);
|
|
BOOST_TEST(vamp_zero::menu_row(state, 0U).label == "Performance");
|
|
BOOST_TEST(vamp_zero::menu_row(state, 1U).label == "Settings");
|
|
state = vamp_zero::apply_menu_action(state, action(state, 1U));
|
|
BOOST_CHECK(state.mode == vamp_zero::app_mode::SETTINGS_MENU);
|
|
BOOST_TEST(state.selected_settings_index == 1U);
|
|
state = vamp_zero::apply_menu_action(state, action(state, 1U));
|
|
BOOST_TEST(!state.audio.backing_enabled);
|
|
state = vamp_zero::apply_menu_action(state, action(state, 2U));
|
|
BOOST_TEST(state.audio.metronome_enabled);
|
|
state = vamp_zero::apply_menu_action(state, action(state, 3U));
|
|
BOOST_TEST(state.audio.tempo_editing);
|
|
state = vamp_zero::apply_menu_action(state, action(state, 0U));
|
|
BOOST_CHECK(state.mode == vamp_zero::app_mode::MAIN_MENU);
|
|
BOOST_TEST(!state.audio.tempo_editing);
|
|
state = vamp_zero::apply_app_command(state, vamp_protocol::page_command::SELECT);
|
|
BOOST_CHECK(state.mode == vamp_zero::app_mode::SETTINGS_MENU);
|
|
BOOST_TEST(state.selected_settings_index == 3U);
|
|
BOOST_TEST(!state.audio.tempo_editing);
|
|
BOOST_TEST(!state.audio.backing_enabled);
|
|
BOOST_TEST(state.audio.metronome_enabled);
|
|
BOOST_REQUIRE(vamp_zero::validate_app_state(state));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(menu_touch_activates_once_on_release_and_stepper_has_priority) {
|
|
auto state = vamp_zero::app_state{};
|
|
auto touch = vamp_zero::menu_touch{};
|
|
auto now = std::chrono::steady_clock::now();
|
|
auto point = row_center(state, 1U);
|
|
BOOST_CHECK(!touch.handle(point_event(luma::touch_phase::DOWN, point, now), menu_bounds, state));
|
|
auto tapped = touch.handle(point_event(luma::touch_phase::UP, point, now + std::chrono::milliseconds{40}), menu_bounds, state);
|
|
BOOST_REQUIRE(tapped);
|
|
state = vamp_zero::apply_menu_action(state, *tapped);
|
|
BOOST_CHECK(state.mode == vamp_zero::app_mode::SETTINGS_MENU);
|
|
BOOST_TEST(!state.audio.backing_enabled);
|
|
BOOST_CHECK(!touch.handle(point_event(luma::touch_phase::UP, point, now), menu_bounds, state));
|
|
point = row_center(state, 3U, vamp_zero::menu_action_kind::TEMPO_UP);
|
|
touch.handle(point_event(luma::touch_phase::DOWN, point, now), menu_bounds, state);
|
|
tapped = touch.handle(point_event(luma::touch_phase::UP, point, now + std::chrono::milliseconds{40}), menu_bounds, state);
|
|
BOOST_REQUIRE(tapped);
|
|
BOOST_CHECK(tapped->kind == vamp_zero::menu_action_kind::TEMPO_UP);
|
|
state = vamp_zero::apply_menu_action(state, *tapped);
|
|
BOOST_TEST(state.audio.tempo_bpm == 120U);
|
|
BOOST_TEST(!state.audio.tempo_editing);
|
|
for (auto i = 0U; i < 100U; ++i) {
|
|
state = vamp_zero::apply_menu_action(state, action(state, 3U, vamp_zero::menu_action_kind::TEMPO_DOWN));
|
|
}
|
|
BOOST_TEST(state.audio.tempo_bpm == 60U);
|
|
for (auto i = 0U; i < 100U; ++i) {
|
|
state = vamp_zero::apply_menu_action(state, action(state, 3U, vamp_zero::menu_action_kind::TEMPO_UP));
|
|
}
|
|
BOOST_TEST(state.audio.tempo_bpm == 120U);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(menu_touch_cancels_drag_hold_multitouch_and_stale_press) {
|
|
for (auto scenario = 0U; scenario < 5U; ++scenario) {
|
|
auto state = vamp_zero::app_state{};
|
|
auto touch = vamp_zero::menu_touch{};
|
|
auto now = std::chrono::steady_clock::now();
|
|
auto point = row_center(state, 1U);
|
|
touch.handle(point_event(luma::touch_phase::DOWN, point, now), menu_bounds, state);
|
|
if (scenario == 0U) {
|
|
touch.handle(point_event(luma::touch_phase::MOVE, {point.x + 30.0F, point.y}, now), menu_bounds, state);
|
|
} else if (scenario == 1U) {
|
|
now += std::chrono::seconds{1};
|
|
} else if (scenario == 2U) {
|
|
touch.handle(point_event(luma::touch_phase::DOWN, point, now, 2), menu_bounds, state);
|
|
touch.handle(point_event(luma::touch_phase::UP, point, now, 2), menu_bounds, state);
|
|
} else if (scenario == 3U) {
|
|
state = vamp_zero::apply_app_command(state, vamp_protocol::page_command::NEXT);
|
|
} else {
|
|
touch.handle(point_event(luma::touch_phase::CANCEL, point, now), menu_bounds, state);
|
|
}
|
|
BOOST_CHECK(!touch.handle(point_event(luma::touch_phase::UP, point, now + std::chrono::milliseconds{40}), menu_bounds, state));
|
|
touch.handle(point_event(luma::touch_phase::DOWN, point, now), menu_bounds, state);
|
|
BOOST_REQUIRE(touch.handle(point_event(luma::touch_phase::UP, point, now + std::chrono::milliseconds{40}), menu_bounds, state));
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(control_rejects_delayed_actions_after_remote_navigation_or_catalog_change) {
|
|
auto state = vamp_zero::app_state{};
|
|
auto control = vamp_zero::control_state{{}, state};
|
|
auto tapped = action(state, 1U);
|
|
control.heartbeat();
|
|
control.apply(tapped);
|
|
BOOST_CHECK(control.snapshot().mode == vamp_zero::app_mode::SETTINGS_MENU);
|
|
control.apply(tapped);
|
|
BOOST_TEST(!control.snapshot().audio.backing_enabled);
|
|
auto settings = control.snapshot();
|
|
tapped = action(settings, 1U);
|
|
control.apply(vamp_protocol::page_command::NEXT);
|
|
auto before = control.snapshot();
|
|
control.apply(tapped);
|
|
BOOST_TEST(control.snapshot().input_revision == before.input_revision);
|
|
BOOST_TEST(!control.snapshot().audio.backing_enabled);
|
|
tapped = action(before, 1U);
|
|
tapped.catalog = std::make_shared<vamp_zero::catalog const>();
|
|
control.apply(tapped);
|
|
BOOST_TEST(control.snapshot().input_revision == before.input_revision);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(menu_scroll_reaches_every_song_and_remote_shows_menu_titles) {
|
|
auto catalog = vamp_zero::catalog{};
|
|
for (auto i = 0U; i < 20U; ++i) {
|
|
catalog.parts.push_back({.id = std::to_string(i), .title = "Song " + std::to_string(i), .part = "Electric", .total_pages = 3U, .title_image = std::string(64U, 'a')});
|
|
catalog.groups.push_back({catalog.parts.back().title, "Composer", {i}});
|
|
}
|
|
auto state = vamp_zero::app_state{.catalog = std::make_shared<vamp_zero::catalog const>(std::move(catalog)), .mode = vamp_zero::app_mode::PERFORMANCE_SONG_MENU};
|
|
for (auto index = 1U; index <= 20U; ++index) {
|
|
state = vamp_zero::apply_menu_action(state, action(state, 0U, vamp_zero::menu_action_kind::NEXT));
|
|
BOOST_TEST(state.selected_song_index == index);
|
|
auto targets = vamp_zero::menu_layout(menu_bounds, state);
|
|
BOOST_TEST(std::any_of(targets.begin(), targets.end(), [&](auto const &target) { return target.action.kind == vamp_zero::menu_action_kind::ACTIVATE && target.action.row == index; }));
|
|
auto remote = vamp_pico::remote_display_state{};
|
|
BOOST_REQUIRE(vamp_pico::parse_remote_display_body(vamp_zero::remote_display_text(state), remote));
|
|
BOOST_TEST(!remote.active);
|
|
BOOST_TEST(std::string{remote.title} == "Song " + std::to_string(index - 1U));
|
|
BOOST_TEST(std::string{remote.title_image.data()} == std::string(64U, 'a'));
|
|
}
|
|
state = vamp_zero::apply_menu_action(state, action(state, 20U));
|
|
state = vamp_zero::apply_menu_action(state, action(state, 1U));
|
|
BOOST_CHECK(state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE);
|
|
BOOST_TEST(state.active_part_id == "19");
|
|
BOOST_TEST(vamp_zero::menu_layout(menu_bounds, state).empty());
|
|
state = vamp_zero::apply_app_command(state, vamp_protocol::page_command::SELECT);
|
|
BOOST_CHECK(state.mode == vamp_zero::app_mode::PERFORMANCE_PART_MENU);
|
|
BOOST_TEST(state.selected_part_index == 1U);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_settings_parser_rejects_invalid_values_without_mutating_state) {
|
|
auto state = vamp_pico::remote_display_state{};
|
|
BOOST_REQUIRE(vamp_pico::parse_remote_display_body("mode=settings_menu\nfocus=Backing\nbacking=1\nbpm=135\n", state));
|
|
auto before = state;
|
|
for (auto text : {"bpm=0\n", "bpm=401\n", "bpm=-1\n", "editing=yes\n", "backing=2\n", "metronome=true\n", "focus=abcdefghijklmnopqrstuvwxyz0123456789\n"}) {
|
|
BOOST_CHECK(!vamp_pico::parse_remote_display_body(text, state));
|
|
BOOST_CHECK(state == before);
|
|
}
|
|
}
|