Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions Assets/Tests/PlayMode/MainMenuTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,16 +361,25 @@ private static IEnumerator LoadBootstrapScene()

private static UIDocument MenuDocument()
{
var document = Object.FindAnyObjectByType<UIDocument>();
Assert.NotNull(document,
"no UIDocument in the Bootstrap scene: the MainMenu object is missing. The scene " +
// By GameObject name, not FindAnyObjectByType: the scene has TWO
// UIDocuments since the version badge (#103) — which one "any"
// returns is undefined, and the badge's document has neither a
// "menu-screen" nor an AudioSource.
UIDocument[] documents = Object.FindObjectsByType<UIDocument>(FindObjectsInactive.Include);
foreach (UIDocument document in documents)
{
if (document.gameObject.name != "MainMenu") continue;
Assert.NotNull(document.panelSettings,
"the menu UIDocument has no PanelSettings, so it has no panel and draws nothing. " +
"MenuAssetSetup.LoadOrCreatePanelSettings creates " +
"Assets/_Project/UI/HashkriegPanelSettings.asset when the generator runs.");
return document;
}
Assert.Fail(
"no UIDocument on a 'MainMenu' object in the Bootstrap scene. The scene " +
"is machine output — run Tools/Project Nova/Create Bootstrap Scene " +
"(BootstrapSceneGenerator.CreateMainMenuObject).");
Assert.NotNull(document.panelSettings,
"the menu UIDocument has no PanelSettings, so it has no panel and draws nothing. " +
"MenuAssetSetup.LoadOrCreatePanelSettings creates " +
"Assets/_Project/UI/HashkriegPanelSettings.asset when the generator runs.");
return document;
return null;
}

private static VisualElement MenuRoot()
Expand Down
130 changes: 130 additions & 0 deletions Assets/Tests/PlayMode/VersionBadgeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using UnityEngine.UIElements;

namespace Nova.PlayMode.Tests
{
/// <summary>
/// Proof that the version badge (issue #103) is always on screen with the
/// exact string the sprint pins down — in the Editor
/// "v&lt;version&gt; · dev (Editor)" — that it never picks, and that it
/// survives the transition the IMGUI cockpit does not (issue #102): the
/// way from the menu into the match.
/// <para>
/// SAME ASSEMBLY-WALL PATTERN AS MainMenuTests: VersionBadge lives in
/// Nova.Presentation.UI (rank 4), which no test assembly may reference
/// (quality/scripts/run_gate_check.py:183-188). Everything below is found
/// by GameObject name and element name; a rename fails loudly here.
/// </para>
/// </summary>
public sealed class VersionBadgeTests
{
private const string ScenePath = "Assets/_Project/Scenes/Bootstrap.unity";
private const string MenuObjectName = "MainMenu";
private const string BadgeObjectName = "VersionBadge";
private const string BadgeElementName = "version-badge";

[UnityTest]
public IEnumerator VersionBadge_IsAlwaysThereNamesTheBuildAndNeverPicks()
{
yield return LoadBootstrapScene();

UIDocument badgeDocument = DocumentOn(BadgeObjectName);
UIDocument menuDocument = DocumentOn(MenuObjectName);
Label badge = BadgeLabel(badgeDocument);

Assert.AreEqual($"v{Application.version} · dev (Editor)", badge.text,
"the badge must name the running version — Application.version, i.e. " +
"ProjectSettings' bundleVersion, the ONE source — plus the build id. In the " +
"Editor BuildInfo.Commit reports its editor sentinel (D-094), so the id is " +
"'dev' with the '(Editor)' marker the sprint specifies.");
Assert.AreEqual(PickingMode.Ignore, badge.pickingMode,
"the badge must never pick: a click in the bottom-left corner belongs to the " +
"minimap or the world behind it, not to a read-only label");
Assert.AreNotSame(menuDocument, badgeDocument,
"the badge needs its OWN UIDocument: the menu clears and rebuilds its root on " +
"every return (MainMenuController.BuildTree), and the IMGUI cockpit is toggled " +
"across the menu/match transition (#102) — a label in either layer would " +
"vanish with it");
Assert.Greater(badgeDocument.sortingOrder, menuDocument.sortingOrder,
"the badge's document must sort above the menu's: the menu paints full-screen " +
"key art plus scrim, and a badge under them is invisible exactly where the " +
"sprint wants it seen");

// Into the match: the menu overlay hides, the badge stays. This
// is the regression the own-document rule exists for.
Button newGame = FindButton(menuDocument.rootVisualElement, "Neues Spiel");
using (var submit = new NavigationSubmitEvent { target = newGame })
{
newGame.SendEvent(submit);
}
yield return null;

Assert.AreEqual(DisplayStyle.None,
menuDocument.rootVisualElement.Q("menu-screen").style.display.value,
"sanity check: starting a match must hide the menu overlay");
Assert.NotNull(badgeDocument.rootVisualElement.Q(BadgeElementName),
"the badge must still be there with the match running — it is not part of " +
"the layer the menu switches off");
Assert.AreNotEqual(DisplayStyle.None, badge.style.display.value,
"nothing may hide the badge on the way into the match");
}

private static IEnumerator LoadBootstrapScene()
{
yield return SceneManager.LoadSceneAsync(ScenePath, LoadSceneMode.Single);

// Awake/OnEnable run during activation, Start one frame later —
// and VersionBadge builds its label in Start. Nothing above may
// query the panel before this.
yield return null;
yield return null;
}

private static Label BadgeLabel(UIDocument badgeDocument)
{
Assert.NotNull(badgeDocument.rootVisualElement,
"the badge UIDocument has no root visual element");
Label badge = badgeDocument.rootVisualElement.Q<Label>(BadgeElementName);
Assert.NotNull(badge,
$"no Label named '{BadgeElementName}' in the badge document — VersionBadge " +
"builds it in Start(). If the element was renamed, this test has to follow: " +
"it cannot reference the type (rank-4 assembly wall).");
return badge;
}

private static UIDocument DocumentOn(string gameObjectName)
{
// FindObjectsInactive.Include: this finder must not silently
// change its answer the day someone deactivates a UI object.
UIDocument[] documents = Object.FindObjectsByType<UIDocument>(FindObjectsInactive.Include);
foreach (UIDocument document in documents)
{
if (document.gameObject.name != gameObjectName) continue;
Assert.NotNull(document.panelSettings,
$"the '{gameObjectName}' UIDocument has no PanelSettings, so it has no panel " +
"and draws nothing. MenuAssetSetup.LoadOrCreatePanelSettings creates " +
"Assets/_Project/UI/HashkriegPanelSettings.asset when the generator runs.");
return document;
}
Assert.Fail(
$"no UIDocument on a '{gameObjectName}' object in the Bootstrap scene. The scene " +
"is machine output — run Tools/Project Nova/Create Bootstrap Scene " +
"(BootstrapSceneGenerator).");
return null;
}

private static Button FindButton(VisualElement root, string text)
{
foreach (Button button in root.Query<Button>().ToList())
{
if (button.text == text) return button;
}
Assert.Fail($"the menu has no button labelled '{text}'");
return null;
}
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/PlayMode/VersionBadgeTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions Assets/_Project/Editor/BootstrapSceneGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public static void CreateBootstrapScene()
CreateMapObject(runner, ground);
GameObject ui = CreateUiObject(runner, camera);
CreateMainMenuObject(runner, camera, ui, audio.MusicGroup);
CreateVersionBadgeObject();
EnsureGlutrinneMapAsset();

// The drop-in registry is rebuilt from whatever PF_* prefabs
Expand Down Expand Up @@ -440,6 +441,28 @@ private static void CreateMainMenuObject(
CreateIngameMusicObject(runner, menu, musicGroup);
}

/// <summary>
/// The always-on version badge (issue #103): its OWN GameObject and
/// UIDocument, so it survives both the menu's root rebuilds and the
/// IMGUI cockpit's menu/match visibility switches (issue #102). It
/// shares the menu's PanelSettings; the component sorts its document
/// above the menu's full-screen key art itself. No visualTreeAsset —
/// like the menu, the tree is built in C# so the generated scene
/// carries nothing this generator could not reproduce.
/// </summary>
private static void CreateVersionBadgeObject()
{
var badgeObject = new GameObject("VersionBadge");

UIDocument document = badgeObject.AddComponent<UIDocument>();
document.panelSettings = MenuAssetSetup.LoadOrCreatePanelSettings();

VersionBadge badge = badgeObject.AddComponent<VersionBadge>();
WireReference(badge, "_document", document);
WireReference(badge, "_font",
MenuAssetSetup.LoadRequired<Font>(MenuAssetSetup.BodyFontPath));
}

/// <summary>
/// The in-game playlist (D-086): own GameObject, own AudioSource —
/// MusicDirector configures the source itself, this file only wires
Expand Down
Loading
Loading