fwt_software/tests/test_uisnapshot.cpp

129 lines
5.0 KiB
C++

#include <doctest/doctest.h>
#include "fgc/mock/MockCameraSource.h"
#include "fgc/ui/UiSnapshot.h"
using namespace fgc;
TEST_CASE("axisStateLabel maps every state") {
CHECK(std::string(axisStateLabel(AxisState::Boot)) == "BOOT");
CHECK(std::string(axisStateLabel(AxisState::Reset)) == "RESET");
CHECK(std::string(axisStateLabel(AxisState::Homing)) == "HOMING");
CHECK(std::string(axisStateLabel(AxisState::Ready)) == "READY");
CHECK(std::string(axisStateLabel(AxisState::Error)) == "ERROR");
CHECK(std::string(axisStateLabel(AxisState::Unknown)) == "----");
}
TEST_CASE("axisStateColor uses one hue, reserving red for a fault") {
// Healthy states share the panel accent so nothing but a fault changes
// colour; Boot/Reset stay dim, which is brightness rather than a second hue.
CHECK(axisStateColor(AxisState::Ready) == UiColor::Cyan);
CHECK(axisStateColor(AxisState::Homing) == UiColor::Cyan);
CHECK(axisStateColor(AxisState::Error) == UiColor::Red);
CHECK(axisStateColor(AxisState::Boot) == UiColor::Dim);
CHECK(axisStateColor(AxisState::Reset) == UiColor::Dim);
CHECK(axisStateColor(AxisState::Unknown) == UiColor::Default);
}
TEST_CASE("formatDegrees: one decimal, sign preserved, degree sign appended") {
CHECK(formatDegrees(12.34) == "12.3\xC2\xB0");
CHECK(formatDegrees(-4.0) == "-4.0\xC2\xB0");
CHECK(formatDegrees(0.0) == "0.0\xC2\xB0");
}
TEST_CASE("formatTimeAgo: buckets and the never case") {
CHECK(formatTimeAgo(10'000, 0) == "\xE2\x80\x94"); // never
CHECK(formatTimeAgo(10'000, 7'000) == "3s ago"); // seconds
CHECK(formatTimeAgo(200'000, 10'000) == "3m ago"); // minutes
CHECK(formatTimeAgo(10'000'000, 1'000'000) == "2h ago"); // hours
CHECK(formatTimeAgo(5'000, 9'000) == "0s ago"); // future clamps to 0
}
TEST_CASE("MockCameraSource::deviceInfo reports identity, dims, streaming, frame count") {
MockCameraSource cam(1, 320, 240);
cam.start();
auto info = cam.deviceInfo();
REQUIRE(info.size() == 1);
CHECK(info[0].model == "MockCamera");
CHECK(info[0].streaming == true);
CHECK(info[0].width == 320);
CHECK(info[0].height == 240);
CHECK(info[0].frames_delivered == 0);
int got = 0;
cam.setFrameCallback([&](const Frame&) { ++got; });
cam.trigger();
CHECK(got == 1);
CHECK(cam.deviceInfo()[0].frames_delivered == 1);
cam.stop();
CHECK(cam.deviceInfo()[0].streaming == false);
}
TEST_CASE("pendingSensorsView: grouped by sensor, all absent until drivers land") {
SensorsView v = pendingSensorsView();
CHECK_FALSE(v.imu.present);
CHECK_FALSE(v.env.present);
CHECK_FALSE(v.imu.title.empty());
CHECK_FALSE(v.env.title.empty());
CHECK(v.imu.fields.size() >= 3); // roll/pitch/yaw
CHECK(v.env.fields.size() >= 2); // ambient temp + humidity
for (const auto* g : {&v.imu, &v.env}) {
for (const auto& f : g->fields) {
CHECK_FALSE(f.present);
CHECK_FALSE(f.label.empty());
}
}
}
TEST_CASE("compact sensors panel shows exactly one temperature, the ambient one") {
SensorsView v = pendingSensorsView();
// The MTi's internal temperature is expanded-view-only, so the main window
// never shows two temperatures the operator has to attribute.
for (const auto& f : v.imu.fields) CHECK(f.label != "Temp");
auto findField = [](const SensorGroup& g, const std::string& label) -> const SensorField* {
for (const auto& f : g.fields)
if (f.label == label) return &f;
return nullptr;
};
const SensorField* temp = findField(v.env, "Temp");
const SensorField* humid = findField(v.env, "Humid");
REQUIRE(temp != nullptr);
REQUIRE(humid != nullptr);
CHECK(temp->unit == "\xC2\xB0""C");
CHECK(humid->unit == "%RH");
}
TEST_CASE("EnvView defaults to disabled with no reading") {
EnvView e;
CHECK_FALSE(e.enabled);
CHECK_FALSE(e.present);
CHECK_FALSE(e.mock);
CHECK(e.timestamp_ms == 0);
}
TEST_CASE("topicLeaf strips the constant GGS/FWT/<tower>/ prefix") {
CHECK(topicLeaf("GGS/FWT/Staeffelsberg/CamEvent") == "CamEvent");
CHECK(topicLeaf("CamEvent") == "CamEvent"); // already a leaf
CHECK(topicLeaf("a/b/") == ""); // trailing slash: empty leaf, not "b"
CHECK(topicLeaf("") == "");
}
TEST_CASE("formatClockMs renders wall-clock time, and never for 0") {
CHECK(formatClockMs(0) == "\xE2\x80\x94");
const std::string t = formatClockMs(1719312345678LL);
REQUIRE(t.size() == 12); // HH:MM:SS.mmm
CHECK(t[2] == ':');
CHECK(t[5] == ':');
CHECK(t[8] == '.');
CHECK(t.substr(9) == "678"); // the ms field comes straight off the input
}
TEST_CASE("backgroundAvailable only accepts a set, non-empty TMUX") {
CHECK(backgroundAvailable("/tmp/tmux-1000/default,12345,0"));
CHECK_FALSE(backgroundAvailable(nullptr)); // not under tmux at all
CHECK_FALSE(backgroundAvailable("")); // set but names no server
}