// Headless tests of the HUD model seam (UI/DescentHudModel.h, UI/DescentHudSubsystem.h). #include "Tests/DescentTestCommon.h" #include "Core/DescentSimSubsystem.h" #include "Debug/DescentStateRegistry.h" #include "UI/DescentHudModel.h" #include "UI/DescentHudSubsystem.h" #if WITH_DEV_AUTOMATION_TESTS IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentHudModelTest, "Descent.Hud.Model", DESCENT_TEST_FLAGS) bool FDescentHudModelTest::RunTest(const FString& Parameters) { FDescentHudModel M; // Section 8.5: a stack of three toasts; the oldest goes. for (int32 I = 1; I <= 5; ++I) { M.PushToast(FString::Printf(TEXT("toast %d"), I), EDescentHudToastKind::Warn, 4.f); } TestEqual(TEXT("three toasts at most"), M.Toasts.Num(), FDescentHudModel::MaxToasts); TestEqual(TEXT("the oldest were dropped"), M.Toasts[0].Text, FString(TEXT("toast 3"))); TestEqual(TEXT("the newest is last"), M.Toasts.Last().Text, FString(TEXT("toast 5"))); // Toasts and the radio line count down and disappear. M.Toasts.Reset(); M.PushToast(TEXT("short"), EDescentHudToastKind::Info, 1.f); M.PushToast(TEXT("long"), EDescentHudToastKind::Good, 5.f); M.SetRadio(TEXT("CANTICLE, this is the bridge."), 2.f); M.Advance(0.9f); TestEqual(TEXT("both toasts alive at 0.9 s"), M.Toasts.Num(), 2); TestFalse(TEXT("radio line alive"), M.Radio.IsEmpty()); M.Advance(0.2f); TestEqual(TEXT("the short toast expired at 1.1 s"), M.Toasts.Num(), 1); TestEqual(TEXT("the long one is left"), M.Toasts[0].Text, FString(TEXT("long"))); M.Advance(1.0f); TestTrue(TEXT("the radio line is cleared after its 2 s"), M.Radio.IsEmpty()); // A radio line with no time limit stays until it is cleared. M.SetRadio(TEXT("standing line"), 0.f); M.Advance(100.f); TestEqual(TEXT("a line without a timer stays"), M.Radio, FString(TEXT("standing line"))); // Formatting helpers. TestEqual(TEXT("thousands"), FDescentHudModel::FormatThousands(1810.4), FString(TEXT("1,810"))); TestEqual(TEXT("thousands, round up"), FDescentHudModel::FormatThousands(3104.6), FString(TEXT("3,105"))); TestEqual(TEXT("thousands, small"), FDescentHudModel::FormatThousands(42.0), FString(TEXT("42"))); TestEqual(TEXT("thousands, millions"), FDescentHudModel::FormatThousands(1234567.0), FString(TEXT("1,234,567"))); TestEqual(TEXT("thousands, negative"), FDescentHudModel::FormatThousands(-1500.0), FString(TEXT("-1,500"))); TestEqual(TEXT("depth text"), FDescentHudModel::FormatDepth(1810.0), FString(TEXT("1,810 m"))); const FString Cabin = FDescentHudModel::FormatTemperature(26.0, TEXT("CABIN")); TestTrue(TEXT("cabin text starts CABIN 26.0"), Cabin.StartsWith(TEXT("CABIN 26.0 "))); TestTrue(TEXT("cabin text ends with degree C"), Cabin.EndsWith(TEXT("C")) && Cabin.Contains(TEXT("°"))); // Reset returns to the defaults the design states. M.Vitals.Nutrition = 3.f; M.Warnings.bShear = true; M.Extras.Add(TEXT("x"), TEXT("y")); M.Reset(); TestEqual(TEXT("reset: nutrition back to the start value"), M.Vitals.Nutrition, 42.f); TestFalse(TEXT("reset: no warnings"), M.Warnings.bShear); TestEqual(TEXT("reset: no extras"), M.Extras.Num(), 0); TestEqual(TEXT("default cabin temperature is 26"), M.TemperatureC, 26.0, 0.0); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentHudSubsystemTest, "Descent.Hud.Subsystem", DESCENT_TEST_FLAGS) bool FDescentHudSubsystemTest::RunTest(const FString& Parameters) { FDescentTestWorld TW; UDescentHudSubsystem* Hud = TW.Sub(); UDescentSimSubsystem* Sim = TW.Sub(); if (!TestNotNull(TEXT("the HUD subsystem exists in a game world"), Hud) || !TestNotNull(TEXT("and so does the sim it depends on"), Sim)) { return false; } // The model's own housekeeping runs from the late step: a toast pushed now is gone after its time of late steps. Hud->Model().PushToast(TEXT("hello"), EDescentHudToastKind::Info, 0.5f); TestEqual(TEXT("toast present"), Hud->Model().Toasts.Num(), 1); for (int32 I = 0; I < 20; ++I) { Sim->LateStep(1.0 / 60.0); } TestEqual(TEXT("still there at 1/3 s"), Hud->Model().Toasts.Num(), 1); for (int32 I = 0; I < 20; ++I) { Sim->LateStep(1.0 / 60.0); } TestEqual(TEXT("gone after 0.5 s of late steps"), Hud->Model().Toasts.Num(), 0); TestEqual(TEXT("40 housekeeping calls"), Hud->HousekeepingCount(), static_cast(40)); // A gameplay system's own HudModel callback runs after the housekeeping and can write the model. FDescentSimRegisterOptions O; O.Owner = TEXT("TestWriter"); Sim->RegisterLateStep(EDescentLateStep::HudModel, [Hud](const FDescentSimFrame&) { Hud->Model().DepthText = FDescentHudModel::FormatDepth(1810.0); Hud->Model().Band = TEXT("THE THROAT"); }, O); Sim->LateStep(0.016); TestEqual(TEXT("the writer filled the depth text"), Hud->Model().DepthText, FString(TEXT("1,810 m"))); // The `ui.hud` part of the state dump reads it. const TSharedRef Snap = FDescentStateRegistry::Get().BuildSnapshot(TW.Get(), TEXT("hud_test")); const TSharedPtr* Ui = nullptr; if (TestTrue(TEXT("ui section"), Snap->TryGetObjectField(TEXT("ui"), Ui))) { const TSharedPtr* HudJson = nullptr; if (TestTrue(TEXT("ui.hud part"), (*Ui)->TryGetObjectField(TEXT("hud"), HudJson))) { TestTrue(TEXT("hud available"), (*HudJson)->GetBoolField(TEXT("available"))); TestEqual(TEXT("depth_text"), (*HudJson)->GetStringField(TEXT("depth_text")), FString(TEXT("1,810 m"))); TestEqual(TEXT("band"), (*HudJson)->GetStringField(TEXT("band")), FString(TEXT("THE THROAT"))); } } return true; } #endif // WITH_DEV_AUTOMATION_TESTS