Files
Tushar Rohilla e5295f36aa Add Afterglow 1.0.6, a PS5 internet radio and music visualizer
222 stations in 17 genres, 56 visualizer scenes, Ogg and Opus playback, station logos, crossfade, My Music from USB, a command box and live BPM sync.
2026-10-16 00:59:10 -04:00

184 lines
5.6 KiB
C++

// ps5-homebrew-ui - Behaviour tests for the Phosphor terminal design.
// Copyright (C) 2026 BlackBearReloaded
// SPDX-License-Identifier: GPL-3.0-or-later
//
// The terminal has more modes than most designs (boot, menu, an opened page,
// a prompt, power-off), so the tests follow the player through them and
// check the sounds each step asks for and where it places them.
#include "concept_fixture.hpp"
#include "concepts/concepts.hpp"
namespace
{
using hui::Action;
using hui::Direction;
using hui::audio::Cue;
class Terminal : public hui::testing::ConceptFixture
{
protected:
// Starts the design and skips its boot sequence: the menu has the input.
void boot()
{
design_->enter();
send(*design_, press(Action::confirm));
}
// Lets the design run with no input and counts how often it asks for a cue.
int count_cues(Cue cue, float seconds)
{
int count = 0;
for (int frame = 0, frames = static_cast<int>(seconds / (1.0f / 60.0f)); frame < frames;
++frame)
{
feedback_.clear();
design_->update(idle(), 1.0f / 60.0f, feedback_);
for (const hui::audio::CueEvent &event : feedback_.cues)
count += event.cue == cue ? 1 : 0;
}
return count;
}
// Stereo position of a cue the last send() asked for (0 if it did not).
float pan_of(Cue cue) const
{
for (const hui::audio::CueEvent &event : last_cues_)
{
if (event.cue == cue)
return event.pan;
}
return 0.0f;
}
void move_to_shutdown()
{
for (int i = 0; i < 6; ++i)
send(*design_, nav(Direction::down), 0.1f);
}
std::unique_ptr<hui::app::Concept> design_ = hui::concepts::make_terminal(context_);
};
TEST_F(Terminal, TheBootTypesWithFewKeySoundsAndAnyButtonSkipsIt)
{
design_->enter();
// Text appears at up to ninety characters a second; the key sound must
// not: twelve a second at most.
const int typed = count_cues(Cue::type, 1.0f);
EXPECT_GE(typed, 3);
EXPECT_LE(typed, 12);
expect_drawable(*design_);
send(*design_, press(Action::north));
EXPECT_TRUE(played(Cue::select));
// The menu has the input now.
send(*design_, nav(Direction::down));
EXPECT_TRUE(played(Cue::tick));
}
TEST_F(Terminal, ReducedMotionShowsTheBootTextAtOnceAndSilently)
{
settings_.reduced_motion = true;
design_->enter();
EXPECT_EQ(count_cues(Cue::type, 2.0f), 0);
// The boot has ended by itself.
send(*design_, nav(Direction::down));
EXPECT_TRUE(played(Cue::tick));
expect_drawable(*design_);
}
TEST_F(Terminal, TheMenuTicksOnTheLeftAndItsEndsRefuse)
{
boot();
send(*design_, nav(Direction::down));
EXPECT_TRUE(played(Cue::tick));
EXPECT_LT(pan_of(Cue::tick), 0.0f);
send(*design_, nav(Direction::up));
EXPECT_TRUE(played(Cue::tick));
send(*design_, nav(Direction::up));
EXPECT_TRUE(played(Cue::error));
EXPECT_FALSE(played(Cue::tick));
// There is nothing above the menu to go back to.
send(*design_, press(Action::back));
EXPECT_TRUE(played(Cue::error));
}
TEST_F(Terminal, AHeldDirectionAtTheEdgeStaysQuiet)
{
boot();
hui::InputFrame held = nav(Direction::up);
held.nav_repeat = true;
send(*design_, held);
EXPECT_TRUE(last_cues_.empty());
}
TEST_F(Terminal, CrossOpensThePageWhichTakesTheInputUntilCircle)
{
boot();
send(*design_, press(Action::confirm));
EXPECT_TRUE(played(Cue::select));
// Down now moves the row cursor of the page, on the right of the screen.
send(*design_, nav(Direction::down));
EXPECT_TRUE(played(Cue::tick));
EXPECT_GT(pan_of(Cue::tick), 0.0f);
send(*design_, press(Action::confirm));
EXPECT_TRUE(played(Cue::mark));
send(*design_, press(Action::confirm));
EXPECT_TRUE(played(Cue::erase));
send(*design_, press(Action::back));
EXPECT_TRUE(played(Cue::back));
send(*design_, nav(Direction::down));
EXPECT_LT(pan_of(Cue::tick), 0.0f);
expect_drawable(*design_);
}
TEST_F(Terminal, SquareCyclesThePhosphorColour)
{
boot();
send(*design_, press(Action::west));
EXPECT_TRUE(played(Cue::toggle));
send(*design_, press(Action::confirm));
send(*design_, press(Action::west));
EXPECT_TRUE(played(Cue::toggle));
expect_drawable(*design_);
}
TEST_F(Terminal, ShutdownAsksFirstAndNoIsTheDefault)
{
boot();
move_to_shutdown();
send(*design_, press(Action::confirm));
EXPECT_TRUE(played(Cue::modal_open));
// Confirming without choosing answers "no".
send(*design_, press(Action::confirm));
EXPECT_TRUE(played(Cue::modal_close));
send(*design_, nav(Direction::up));
EXPECT_TRUE(played(Cue::tick));
}
TEST_F(Terminal, ShuttingDownPowersOffAndBootsAgain)
{
boot();
move_to_shutdown();
send(*design_, press(Action::confirm));
send(*design_, nav(Direction::left));
EXPECT_TRUE(played(Cue::tick));
send(*design_, press(Action::confirm), 0.2f);
EXPECT_TRUE(played(Cue::drop));
expect_drawable(*design_); // the picture is collapsing
// The tube ignores the controller while it is off.
send(*design_, nav(Direction::up), 0.6f);
EXPECT_TRUE(last_cues_.empty());
expect_drawable(*design_); // dark
// Then the boot sequence runs again, and a button skips it as before.
EXPECT_GE(count_cues(Cue::type, 1.5f), 1);
send(*design_, press(Action::confirm));
EXPECT_TRUE(played(Cue::select));
// The selection was kept: SHUTDOWN is the last command.
send(*design_, nav(Direction::down));
EXPECT_TRUE(played(Cue::error));
}
} // namespace