using System.Collections; using System.Linq; using NUnit.Framework; using NightclubArcadia.Cinematics; 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. /// 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"); } /// /// Regression: "Can't start dialogue: no Yarn Project has been configured." /// /// Asserting the runner merely exists is not enough — it auto-starts on Start(), /// and a runner with no project throws that at load with nothing else wrong. /// The project is a serialized asset reference supplied by a prefab override, so /// it breaks silently if the override is ever dropped. /// [Test] public void TheDialogueRunner_HasItsYarnProject() { var runners = Object.FindObjectsByType( FindObjectsInactive.Include, FindObjectsSortMode.None); Assert.AreEqual(1, runners.Length, "Expected exactly one DialogueRunner across the loaded scenes, found " + string.Join(", ", runners.Select(r => $"'{r.name}' in {r.gameObject.scene.name}"))); var runner = runners[0]; Assert.IsNotNull(runner.YarnProject, $"'{runner.name}' in scene '{runner.gameObject.scene.name}' has no Yarn Project. " + "It auto-starts, so this is the 'no Yarn Project has been configured' error."); Assert.IsNotEmpty(runner.YarnProject.NodeNames, "The Yarn Project compiled no nodes"); } [Test] public void EveryNpc_ResolvedItsDialogueRunner() { var npcs = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); Assert.IsNotEmpty(npcs, "Expected NPCs in the level scene"); foreach (var npc in npcs) { Assert.IsNotNull(npc.dialogueRunner, $"'{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 binders = Object.FindObjectsByType( FindObjectsInactive.Include, FindObjectsSortMode.None); Assert.IsNotEmpty(binders, "Expected at least one runtime-bound camera"); foreach (var binder in binders) { Assert.IsNotNull(binder.GetComponent().Follow, $"'{binder.name}' never bound its Follow target to the player"); } } } }