using System.Collections;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using Unity.Cinemachine;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using Yarn.Unity;
namespace NightclubArcadia.PlayMode.Tests
{
///
/// Proves the scene split actually comes up.
///
/// Splitting Systems out of the level nulled seven serialized references that
/// Unity cannot carry across a scene boundary — the two NPCs' dialogue runner, UI
/// and player fields, and the reveal camera's tracking target. Each is supposed to
/// be resolved at runtime instead. An EditMode test cannot see any of that, because
/// none of it has run. This can.
///
/// If this suite fails, the game does not start. Treat it that way.
///
/// Two checks reach their types by name through reflection rather than referencing
/// them. NPCStandIn and NightclubArcadia.Cinematics live in Assembly-CSharp, and an
/// assembly-definition test assembly cannot reference the predefined assemblies —
/// only the other way round. Giving the runtime code its own asmdefs
/// (docs/restructure-plan.md Phase 4.1) removes the need for this; until then the
/// reflection is the accommodation, and it is confined to these two tests.
///
public class BootstrapTests
{
const string Bootstrap = "Bootstrap";
const string Systems = "Systems";
const string Level = "SC101_ConferenceHall";
[UnitySetUp]
public IEnumerator LoadFromBootstrap()
{
yield return SceneManager.LoadSceneAsync(Bootstrap, LoadSceneMode.Single);
// GameBootstrap loads the other two additively over a few frames.
var deadline = Time.realtimeSinceStartup + 20f;
while (Time.realtimeSinceStartup < deadline)
{
if (SceneManager.GetSceneByName(Systems).isLoaded &&
SceneManager.GetSceneByName(Level).isLoaded)
{
break;
}
yield return null;
}
// let Awake/Start run on everything that just loaded
for (var i = 0; i < 5; i++)
{
yield return null;
}
}
[Test]
public void Bootstrap_LoadsSystemsAndLevel()
{
Assert.IsTrue(SceneManager.GetSceneByName(Systems).isLoaded, "Systems scene did not load");
Assert.IsTrue(SceneManager.GetSceneByName(Level).isLoaded, "Level scene did not load");
Assert.AreEqual(Level, SceneManager.GetActiveScene().name,
"The level should be the active scene, so new objects and lighting land there");
}
[Test]
public void ThePlayerExists_AndIsTagged()
{
var player = GameObject.FindGameObjectWithTag("Player");
Assert.IsNotNull(player, "No object tagged Player — the reveal camera and the NPCs both need it");
}
[Test]
public void TheDialogueRunnerExists()
{
Assert.IsNotNull(Object.FindFirstObjectByType(),
"No DialogueRunner in any loaded scene");
}
[Test]
public void EveryNpc_ResolvedItsDialogueRunner()
{
var npcs = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)
.Where(m => m != null && m.GetType().Name == "NPCStandIn")
.ToArray();
Assert.IsNotEmpty(npcs, "Expected NPCs in the level scene");
foreach (var npc in npcs)
{
var field = npc.GetType().GetField("dialogueRunner",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
Assert.IsNotNull(field, "NPCStandIn no longer has a dialogueRunner field");
Assert.IsNotNull(field.GetValue(npc) as Object,
$"'{npc.name}' did not resolve a DialogueRunner. It lives in the Systems scene, " +
"so the serialized reference is gone and SceneServices.Resolve has to find it.");
}
}
[Test]
public void RevealCameras_BoundToThePlayer()
{
var bound = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)
.Where(cam => cam.GetComponents()
.Any(m => m != null && m.GetType().Name == "CinemachineFollowsPlayer"))
.ToArray();
Assert.IsNotEmpty(bound, "Expected at least one runtime-bound camera");
foreach (var cam in bound)
{
Assert.IsNotNull(cam.Follow,
$"'{cam.name}' never bound its Follow target to the player");
}
}
}
}