using System.Collections; using System.Linq; using NUnit.Framework; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools; namespace NightclubArcadia.PlayMode.Tests { /// /// The level must survive being loaded BEFORE the systems it depends on. /// /// BootstrapTests only covers the happy order — Bootstrap loads Systems, then the /// level, so by the time an NPC awakes the dialogue runner is already there. The /// Editor does not guarantee that. Open the level on its own, or open scenes in a /// different order, press Play, and NPCStandIn.Awake runs with no Systems scene in /// sight. That produced: /// /// [SceneServices] Bartender: could not resolve dialogueRunner /// /// Resolving a cross-scene dependency in Awake is a race by construction. The fix /// is to resolve at the point of use instead, so any load order works — and this is /// the test that holds that property. /// public class SceneOrderTests { const string Systems = "Systems"; const string Level = "SC101_ConferenceHall"; [UnityTearDown] public IEnumerator TearDown() { yield return SceneManager.LoadSceneAsync("Bootstrap", LoadSceneMode.Single); } [UnityTest] public IEnumerator LevelLoadedBeforeSystems_NpcsStillResolveTheirRunner() { // deliberately the wrong way round yield return SceneManager.LoadSceneAsync(Level, LoadSceneMode.Single); yield return SceneManager.LoadSceneAsync(Systems, LoadSceneMode.Additive); for (var i = 0; i < 3; i++) { yield return null; } var npcs = Object.FindObjectsByType( FindObjectsInactive.Include, FindObjectsSortMode.None); Assert.IsNotEmpty(npcs, "Expected NPCs in the level scene"); // Read the serialized field BEFORE touching the lazy property, which caches // into it. If this is null, Awake really did run with no Systems scene — i.e. // the race this test exists for is genuinely reproduced here, and the // assertion below is not passing for free. var eager = npcs.Count(n => n.dialogueRunner == null); Debug.Log($"[Order] NPCs whose Awake-time resolution failed: {eager}/{npcs.Length}"); Assert.Greater(eager, 0, "Awake-time resolution succeeded, so this test is not reproducing the race " + "it was written for. Rewrite it before trusting it."); foreach (var npc in npcs) { Assert.IsNotNull(npc.ResolvedDialogueRunner, $"'{npc.name}' could not reach the DialogueRunner when the level loaded " + "before Systems. Cross-scene resolution must not depend on load order."); } } } }