Decompiled source of CatLibTests v0.6.2

plugins/CatLib.Tests.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using BetterRepair;
using BetterRepair.Logic;
using BetterRepair.Settings;
using BoatTweaks;
using BoatTweaks.Logic;
using BoatTweaks.Settings;
using CatLib.Config;
using CatLib.Core;
using CatLib.DevTools;
using CatLib.Diagnostics;
using CatLib.Events;
using CatLib.Game;
using CatLib.Game.Bridge;
using CatLib.Game.Events;
using CatLib.Il2Cpp;
using CatLib.Localization;
using CatLib.Logging;
using CatLib.Net;
using CatLib.Saves;
using CatLib.Tests.Diagnostics;
using CatLib.Tests.Diagnostics.Inspection;
using CatLib.Tests.Framework;
using CatLib.Tests.Suites.Localization;
using CatLib.Tests.Suites.Network;
using CatLib.Tests.Suites.Presentation;
using CatLib.Tests.Suites.Saves;
using CatLib.Tests.Suites.Settings;
using CatLib.Tests.Timeline;
using CatLib.Threading;
using CatLib.UI;
using HarmonyLib;
using I2.Loc;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Reflection;
using Microsoft.CodeAnalysis;
using SemanticVersioning;
using ShelfLabels;
using ShelfLabels.Data;
using ShelfLabels.Logic;
using ShelfLabels.Settings;
using ShelfLabels.Sync;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.Diagnostics;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("CatLib contributors")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.6.2.0")]
[assembly: AssemblyInformationalVersion("0.6.2+4162013e7c932bfa1cf0340558d83186f1fd5817")]
[assembly: AssemblyProduct("CatLib")]
[assembly: AssemblyTitle("CatLib.Tests")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.6.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CatLib.Tests
{
	[BepInPlugin("catlib.tests", "CatLib.Tests", "0.6.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class CatLibTestsPlugin : BasePlugin
	{
		private ConfigEntry<bool> _runOnMainMenu;

		private ConfigEntry<float> _fallbackDelaySeconds;

		private ConfigEntry<string> _entityDumpTypes;

		private ConfigEntry<string> _textureNames;

		private EntityInspector _entityInspector;

		private LabelCloneExperiment _labelClone;

		private LocalJoin _localJoin;

		private CrashTrigger _crashes;

		private MessageProbe _probes;

		private SaveProbe _saveProbe;

		private UiHierarchyDumper _uiDumper;

		private SpriteExporter _sprites;

		private StressMods _stressMods;

		private CatLogger _log;

		private TestRunner _runner;

		private TestReportWriter _reportWriter;

		private TimelineRecorder _timeline;

		private bool _autoRunHandled;

		public override void Load()
		{
			_log = CatLogger.From(((BasePlugin)this).Log);
			_runOnMainMenu = ((BasePlugin)this).Config.Bind<bool>("Run", "OnMainMenu", true, "Run all tests automatically the first time the main menu is loaded.");
			_fallbackDelaySeconds = ((BasePlugin)this).Config.Bind<float>("Run", "FallbackDelaySeconds", 45f, "Run all tests after this many seconds if the main menu event was never observed. Set to 0 to disable.");
			_textureNames = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "TextureExportNames", "t_RepairWorkstation_Elements,t_DamagedParcel,t_Tools_BC,t_StorageTags,t_ParcelsConstraint_Icon,t_PackageConstraint_Heavy,t_Boat_Props_BC,t_Boat_Hull_BC,t_WorldsAndStampsMaps_BC,t_Progression_Objetcs_BC,t_Dock_Props_BC,t_Workstations_BC,t_CatStuff_BC", "Comma separated texture names the Texture export command writes as PNG.");
			_entityDumpTypes = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "EntityDumpTypes", "EntityInteractableStore,EntityRepairWorkstation,EntityStorageLabel,ParcelDeliverer,ParcelDeliveryManager", "Comma separated game types to list in every entity dump.");
			string path = Path.Combine(Paths.BepInExRootPath, "CatLib.Tests");
			ConfigSandbox.RootDirectory = Path.Combine(path, "Sandbox");
			ResetSandbox();
			_timeline = new TimelineRecorder(Path.Combine(path, "Timelines"), _log.Scope("Timeline"));
			_timeline.Start();
			_uiDumper = new UiHierarchyDumper(Path.Combine(path, "Dumps"), _log.Scope("UiDump"));
			_sprites = new SpriteExporter(Path.Combine(path, "Dumps"), _log.Scope("Sprites"));
			_stressMods = new StressMods(Path.Combine(path, "Stress"), _log.Scope("Stress"));
			_probes = new MessageProbe(_log.Scope("Probe"));
			_saveProbe = new SaveProbe("0.6.2", _log.Scope("SaveProbe"));
			_labelClone = new LabelCloneExperiment(_log.Scope("LabelClone"));
			_localJoin = new LocalJoin(_log.Scope("LocalJoin"));
			_crashes = new CrashTrigger(_log.Scope("Crash"));
			_entityInspector = new EntityInspector(Path.Combine(path, "Dumps"), () => _entityDumpTypes.Value, _log.Scope("Inspect"));
			_reportWriter = new TestReportWriter(Path.Combine(path, "Reports"));
			_runner = new TestRunner(TestRegistry.Discover(typeof(CatLibTestsPlugin).Assembly), _log.Scope("Runner"));
			_runner.Completed += OnRunCompleted;
			DeclareDemoSettings();
			RegisterDevCommands();
			BootstrapEvents.MainMenuLoaded += OnMainMenuLoaded;
			FrameLoop.Update += OnUpdate;
			_log.Info($"CatLib.Tests {"0.6.2"} loaded with {_runner.TestCount} tests. Run them again from the developer menu.");
		}

		private void RegisterDevCommands()
		{
			DevMenu.Command("Tests", "Run all tests", (Func<string>)delegate
			{
				_runner.Start("DevMenu");
				return $"{_runner.TestCount} test(s) started, the report is written when they finish";
			}, "Runs every test again. They also run by themselves when the main menu loads.");
			DevMenu.Toggle("Tests", "Run on main menu", (Func<bool>)(() => _runOnMainMenu.Value), (Action<bool>)delegate(bool value)
			{
				_runOnMainMenu.Value = value;
			}, "Runs all tests the first time the main menu loads. Saved as OnMainMenu in section [Run] of catlib.tests.cfg.");
			DevMenu.Command("Inspect", "Entity dump", (Func<string>)delegate
			{
				_entityInspector.Dump();
				return "written to BepInEx/CatLib.Tests/Dumps";
			}, "Writes what the camera looks at, with components, fields and object tree, and the nearest objects of the focus types.");
			DevMenu.Command("Inspect", "Settings menu dump", (Func<string>)delegate
			{
				_uiDumper.DumpSettingsMenus();
				return "written to BepInEx/CatLib.Tests/Dumps";
			}, "Writes the hierarchy of the open settings menu.");
			DevMenu.Command("Inspect", "Lobby dump", (Func<string>)delegate
			{
				_uiDumper.DumpLobby();
				return "written to BepInEx/CatLib.Tests/Dumps";
			}, "Writes the hierarchy of the open multiplayer lobby with sizes, fonts, colors and sprites.");
			DevMenu.Command("Inspect", "Sprite export", (Func<string>)(() => "written to " + _sprites.Export()), "Writes every texture that holds loaded UI sprites as PNG, with index.txt naming each sprite and its place. Run it in the menu and in a level.");
			DevMenu.Command("Inspect", "Texture export", (Func<string>)(() => "written to " + _sprites.ExportTextures(_textureNames.Value.Split(','))), "Writes the textures named in TextureExportNames of catlib.tests.cfg as PNG. Run it in a level, where they are loaded.");
			DevMenu.Command("Network", "Session roster", (Func<string>)delegate
			{
				//IL_015b: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
				SessionRoster roster = CatNetwork.Roster;
				if (!(roster == (SessionRoster)null))
				{
					foreach (RosterEntry player in roster.Players)
					{
						_log.Info($"Roster: {player.Name} ({player.Id}){(player.IsHost ? " host" : string.Empty)} {player.Status}, CatLib {player.CatLibVersion}, " + string.Join(", ", player.Mods.Select((RosterMod mod) => $"{mod.Name} {mod.Version} {mod.Mark}")));
					}
					IReadOnlyList<string> readOnlyList = roster.PausedMods((IEnumerable<ModInfo>)CatNetwork.DeclaredMods);
					_log.Info($"Roster: policy {roster.Policy}, active {string.Join(", ", roster.ActiveMods)}, paused here {((readOnlyList.Count == 0) ? "none" : string.Join(", ", readOnlyList))}");
					return $"{roster.Players.Count} player(s), {readOnlyList.Count} paused, see the log";
				}
				return "no session";
			}, "Writes every player of the session with their mods and marks, and which mods are paused.");
			DevMenu.Command("Inspect", "Label clone experiment", (Func<string>)delegate
			{
				_labelClone.Toggle();
				return "see the log";
			}, "Copies the shelf label the camera looks at, press again to remove the copies. Superseded by the Shelf Labels mod.");
			DevMenu.Command("Network", "Mod message probe", (Func<string>)delegate
			{
				_probes.SendPing();
				return "see the log";
			}, "Sends a mod message to the host, which answers every player. Works alone too.");
			DevMenu.Command("Network", "Visible players", (Func<string>)_localJoin.ListPlayers, "Lists the players Steam sees, with their rich presence. On one computer these are the other game copies.");
			DevMenu.Command("Network", "Join the first host", (Func<string>)_localJoin.JoinFirst, "Joins the first visible player that offers a game, the same way accepting a Steam invite does.");
			DevMenu.Command("Network", "Steam self check", (Func<string>)delegate
			{
				SessionNetwork.RunSelfCheck();
				return "see the log";
			}, "Sends a message to yourself through the CatLib Steam channel.");
			DevMenu.Command("UI", "Notification preview", (Func<string>)delegate
			{
				string languageCode = UiText.LanguageCode;
				PlayerMessages.Post(UiText.Format("NetPlayerDisconnected", languageCode, new object[2] { "RENTAI", "CatLib Demo Network Mod 1.0.0 / 2.0.0" }), UiText.Format("NetPlayerDisconnectedBrief", languageCode, new object[2] { "RENTAI", "CatLib Demo Network Mod" }));
				return "posted, it shows as a game notification in a level";
			}, "Posts a sample network message.");
			DevMenu.Command("UI", "Version badge preview", (Func<string>)delegate
			{
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0030: Invalid comparison between Unknown and I4
				//IL_007b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0080: Unknown result type (might be due to invalid IL or missing references)
				GameBuildCheck previewCheck = MenuVersionBadge.PreviewCheck;
				GameBuildStatus? val = ((previewCheck != null) ? new GameBuildStatus?(previewCheck.Status) : ((GameBuildStatus?)null));
				string text = ((!val.HasValue) ? "CMC 1.02.00.1800.9800.100" : (((int)val.GetValueOrDefault() != 3) ? null : "CMC 1.01.00.1700.9722.30000"));
				string text2 = text;
				MenuVersionBadge.PreviewCheck = ((text2 == null) ? null : GameCompatibility.Compare(GameBuild.Parse(text2, 0), GameCompatibility.Supported));
				return (!(MenuVersionBadge.PreviewCheck == (GameBuildCheck)null)) ? ("pretends the game is " + ((object)MenuVersionBadge.PreviewCheck.Status/*cast due to .constrained prefix*/).ToString()) : "the real check";
			}, "Shows the main menu version badge as if the game were newer, then older, then the real check again.");
			DevMenu.Command("UI", "Stress mods on or off", (Func<string>)delegate
			{
				_stressMods.Toggle();
				return "see the Mods tab";
			}, "Creates or removes a set of mods with many settings to check the Mods tab layout.");
			DevMenu.Command("Crash", "Native crash, game thread", (Func<string>)_crashes.NativeOnGameThread, "Crashes the game with a native access violation on the game thread to check the CatLib crash window. Press twice.");
			DevMenu.Command("Crash", "Native crash, worker thread", (Func<string>)_crashes.NativeOnWorkerThread, "Crashes the game from a new native thread, like the Steam networking crash. Press twice.");
			DevMenu.Command("Crash", "Managed crash", (Func<string>)_crashes.Managed, "Crashes the game with an unhandled .NET exception on a new thread. Press twice.");
			DevMenu.Command("Crash", "Hang on quit", (Func<string>)_crashes.ToggleHangOnQuit, "Turns on or off a 90 s hang of the game thread while Unity shuts down after a quit, to check that the crash watcher reports a hang and keeps a dump.");
		}

		private void DeclareDemoSettings()
		{
			CatSettings obj = CatSettings.For((BasePlugin)(object)this);
			obj.Local<string>("Demo", "Message", "Hello from CatLib", "Edit while the game is running to check live reload.", (AcceptableValueBase)null).Apply((Action<string>)delegate(string value)
			{
				_log.Message("Demo.Message is now \"" + value + "\"");
			});
			obj.Local<int>("Demo", "Volume", 50, "Edit while the game is running. Values outside 0-100 are adjusted.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100)).Apply((Action<int>)delegate(int value)
			{
				_log.Message($"Demo.Volume is now {value}");
			});
			obj.Local<bool>("Demo", "FastMode", false, "Changes to this value only apply after a restart.", (AcceptableValueBase)null).RequiresRestart().Apply((Action<bool>)delegate(bool value)
			{
				_log.Message($"Demo.FastMode is {value}");
			});
			obj.Local<string>("Demo", "NetworkModVersion", "1.0.0", "Version of the demo network mod that every player needs. Change it on one side to test a mismatch.", (AcceptableValueBase)null).Apply((Action<string>)delegate(string value)
			{
				CatNetwork.Declare("catlib.tests.demo-network", "CatLib Demo Network Mod", value, (SessionPolicy)1, (VersionRule)2);
				_log.Message("Demo network mod declared as version " + value);
			});
			obj.Session<int>("Demo", "SharedLimit", 3, "A session setting. In multiplayer the host's value applies to everyone.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10)).Apply((Action<int>)delegate(int value)
			{
				_log.Message($"Demo.SharedLimit is now {value}");
			});
		}

		private void ResetSandbox()
		{
			try
			{
				if (Directory.Exists(ConfigSandbox.RootDirectory))
				{
					Directory.Delete(ConfigSandbox.RootDirectory, recursive: true);
				}
			}
			catch (Exception ex)
			{
				_log.Warning("Could not clean " + ConfigSandbox.RootDirectory + ": " + ex.Message);
			}
		}

		private void OnMainMenuLoaded()
		{
			if (!_autoRunHandled && _runOnMainMenu.Value)
			{
				_autoRunHandled = true;
				_runner.Start("MainMenuLoaded");
			}
		}

		private void OnUpdate()
		{
			if (!_autoRunHandled && _runOnMainMenu.Value && _fallbackDelaySeconds.Value > 0f && FrameLoop.Realtime >= (double)_fallbackDelaySeconds.Value)
			{
				_autoRunHandled = true;
				_log.Warning($"Main menu event was not observed within {_fallbackDelaySeconds.Value:0} s, running tests anyway");
				_runner.Start("FallbackTimeout");
			}
			_labelClone.Update();
			_runner.Update();
		}

		private void OnRunCompleted(TestRunSummary summary)
		{
			try
			{
				string text = _reportWriter.Write(summary);
				_log.Message("Test report written to " + text);
			}
			catch (Exception ex)
			{
				_log.Error("Failed to write test report", ex);
			}
		}
	}
	public static class PluginMeta
	{
		public const string Guid = "catlib.tests";

		public const string Name = "CatLib.Tests";

		public const string Version = "0.6.2";
	}
}
namespace CatLib.Tests.Timeline
{
	public sealed class TimelineRecorder
	{
		private readonly string _directory;

		private readonly CatLogger _log;

		private StreamWriter _writer;

		private long _pendingTicks;

		private string _lastTickArguments;

		public string FilePath { get; private set; }

		public TimelineRecorder(string directory, CatLogger log)
		{
			_directory = directory;
			_log = log;
		}

		public void Start()
		{
			if (_writer == null)
			{
				Directory.CreateDirectory(_directory);
				FilePath = Path.Combine(_directory, "timeline_" + InvariantFormat.FileStamp(DateTime.Now) + ".log");
				_writer = new StreamWriter(FilePath, append: false)
				{
					AutoFlush = true
				};
				_writer.WriteLine("CatLib timeline started " + InvariantFormat.Timestamp(DateTime.Now));
				_writer.WriteLine($"CatLib {"0.6.2"}, BepInEx {Paths.BepInExVersion}");
				_writer.WriteLine("     realtime    frame  event");
				GameEventStream.Raised += OnRaised;
				_log.Info("Recording game event timeline to " + FilePath);
			}
		}

		private void OnRaised(GameEventRecord record)
		{
			if (record.Name == "Network.NetworkTick")
			{
				_pendingTicks++;
				_lastTickArguments = record.Arguments;
			}
			else
			{
				FlushTicks(record);
				Write(record.Realtime, record.Frame, record.Name, record.Arguments);
			}
		}

		private void FlushTicks(GameEventRecord next)
		{
			if (_pendingTicks != 0L)
			{
				Write(next.Realtime, next.Frame, "Network.NetworkTick", $"x{_pendingTicks} last {_lastTickArguments}");
				_pendingTicks = 0L;
			}
		}

		public static string FormatLine(double realtime, long frame, string name, string arguments)
		{
			return realtime.ToString("0.000", CultureInfo.InvariantCulture).PadLeft(13) + " " + frame.ToString(CultureInfo.InvariantCulture).PadLeft(8) + "  " + name + (string.IsNullOrEmpty(arguments) ? string.Empty : (" " + arguments));
		}

		private void Write(double realtime, long frame, string name, string arguments)
		{
			string text = FormatLine(realtime, frame, name, arguments);
			try
			{
				_writer.WriteLine(text);
			}
			catch (Exception ex)
			{
				_log.Warning("Failed to write timeline entry: " + ex.Message);
			}
			_log.Info(text.TrimStart());
		}
	}
}
namespace CatLib.Tests.Suites.Ui
{
	public sealed class ContextStripTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 12;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			using UiRowsSandbox sandbox = new UiRowsSandbox("UiContext");
			foreach (TestStep item in SettingsMenuFixture.SelectMod(context, sandbox.Settings))
			{
				yield return item;
			}
			Vector3 pointer = new Vector3(-10000f, -10000f, 0f);
			ModsPanel.PointerOverride = () => pointer;
			try
			{
				ModsPanel controller = SettingsMenuFixture.Tab.Controller;
				EventSystem eventSystem = EventSystem.current;
				Assert.NotNull(eventSystem, "EventSystem.current");
				string language = UiText.LanguageCode;
				SliderRow count = SettingsMenuFixture.Row<SliderRow>("Count");
				eventSystem.SetSelectedGameObject(((Component)((SettingRow)count).Control).gameObject);
				yield return Wait.Frames(2);
				context.Note("Context: " + controller.ContextLabel.text.Replace("\n", " | "));
				Assert.True((object)count == controller.ContextRow, "The selected row must drive the context strip");
				Assert.Equal(ContextText.For((ISetting)(object)sandbox.Count, language), controller.ContextLabel.text, "Context strip text");
				DropdownRow val = SettingsMenuFixture.Row<DropdownRow>("Color");
				eventSystem.SetSelectedGameObject(((Component)((SettingRow)val).Control).gameObject);
				yield return Wait.Frames(2);
				Assert.Equal(ContextText.For((ISetting)(object)sandbox.Color, language), controller.ContextLabel.text, "Context strip follows the selection");
				eventSystem.SetSelectedGameObject((GameObject)null);
				yield return Wait.Frames(2);
				Assert.Equal(UiText.Get("HoverHint", language), controller.ContextLabel.text, "Hint without a focused row and with the pointer away");
				RectTransform val2 = ((Il2CppObjectBase)((SettingRow)SettingsMenuFixture.Row<ToggleRow>("Enabled")).Root.transform).TryCast<RectTransform>();
				Rect rect = val2.rect;
				Vector2 screen = RectTransformUtility.WorldToScreenPoint((Camera)null, ((Transform)val2).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect)).center)));
				RectTransform viewport = ModsTabBuilder.ViewportOf(SettingsMenuFixture.Tab.ContentScroll);
				Assert.True(RectTransformUtility.RectangleContainsScreenPoint(viewport, screen, (Camera)null), $"Test setup: the Enabled row at {screen.x:0},{screen.y:0} must be inside the visible part of the settings pane");
				eventSystem.SetSelectedGameObject(((Component)((SettingRow)count).Control).gameObject);
				yield return Wait.Frames(2);
				pointer = new Vector3(screen.x, screen.y, 0f);
				eventSystem.SetSelectedGameObject((GameObject)null);
				yield return Wait.Frames(2);
				context.Note($"Pointer at {screen.x:0},{screen.y:0}: " + controller.ContextLabel.text.Replace("\n", " | "));
				Assert.Equal(ContextText.For((ISetting)(object)sandbox.Enabled, language), controller.ContextLabel.text, "After a click clears the selection, the row under a still pointer must drive the context strip");
				RectTransform val3 = ((Il2CppObjectBase)((SettingRow)SettingsMenuFixture.Row<TextRow>("Seed")).Root.transform).TryCast<RectTransform>();
				rect = val3.rect;
				Vector2 val4 = RectTransformUtility.WorldToScreenPoint((Camera)null, ((Transform)val3).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect)).center)));
				if (!RectTransformUtility.RectangleContainsScreenPoint(viewport, val4, (Camera)null))
				{
					pointer = new Vector3(val4.x, val4.y, 0f);
					yield return Wait.Frames(2);
					Assert.Equal(UiText.Get("HoverHint", language), controller.ContextLabel.text, "A row scrolled out of the visible area must not react to the pointer");
					context.Note("Checked that a row outside the visible area is ignored");
				}
			}
			finally
			{
				ModsPanel.PointerOverride = null;
				EventSystem current = EventSystem.current;
				if (current != null)
				{
					current.SetSelectedGameObject((GameObject)null);
				}
			}
		}
	}
	public sealed class ControlsRegisteredTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 9;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			using UiRowsSandbox sandbox = new UiRowsSandbox("UiControls");
			foreach (TestStep item in SettingsMenuFixture.SelectMod(context, sandbox.Settings))
			{
				yield return item;
			}
			OptionsInterface options = SettingsMenuFixture.Tab.Options;
			TextRow val = SettingsMenuFixture.Row<TextRow>("Name");
			TMP_InputField input = val.Input;
			TMP_Dropdown dropdown = SettingsMenuFixture.Row<DropdownRow>("Color").Dropdown;
			Il2CppReferenceArray<SelectableInputField> inputFields = options._inputFields;
			Il2CppReferenceArray<SelectableDropdown> dropdowns = options._dropdowns;
			bool flag = false;
			bool flag2 = false;
			int num = 0;
			for (int i = 0; i < ((Il2CppArrayBase<SelectableInputField>)(object)inputFields).Length; i++)
			{
				flag |= (Object)(object)((Il2CppArrayBase<SelectableInputField>)(object)inputFields)[i] != (Object)null && ((Il2CppObjectBase)((Il2CppArrayBase<SelectableInputField>)(object)inputFields)[i]).Pointer == ((Il2CppObjectBase)input).Pointer;
				num += ((!UiClone.IsAlive((Object)(object)((Il2CppArrayBase<SelectableInputField>)(object)inputFields)[i])) ? 1 : 0);
			}
			for (int j = 0; j < ((Il2CppArrayBase<SelectableDropdown>)(object)dropdowns).Length; j++)
			{
				flag2 |= (Object)(object)((Il2CppArrayBase<SelectableDropdown>)(object)dropdowns)[j] != (Object)null && ((Il2CppObjectBase)((Il2CppArrayBase<SelectableDropdown>)(object)dropdowns)[j]).Pointer == ((Il2CppObjectBase)dropdown).Pointer;
				num += ((!UiClone.IsAlive((Object)(object)((Il2CppArrayBase<SelectableDropdown>)(object)dropdowns)[j])) ? 1 : 0);
			}
			context.Note($"Registered input fields: {((Il2CppArrayBase<SelectableInputField>)(object)inputFields).Length}, dropdowns: {((Il2CppArrayBase<SelectableDropdown>)(object)dropdowns).Length}");
			SelectableInputField obj = ((Il2CppObjectBase)input).TryCast<SelectableInputField>();
			string text = ((obj != null) ? obj.BigPictureDescription : null);
			context.Note("Virtual keyboard title of Name: \"" + text + "\"");
			Assert.True(flag, "Mod input fields must be registered in OptionsInterface._inputFields");
			Assert.True(flag2, "Mod dropdowns must be registered in OptionsInterface._dropdowns");
			Assert.Equal(0, num, "Destroyed controls left in the OptionsInterface arrays");
			Assert.Equal(((SettingRow)val).Label.text, text, "The virtual keyboard of a text setting must be titled with the setting's name");
		}
	}
	public sealed class GamepadNavigationTest : TestCase
	{
		private sealed class Mover
		{
			public bool Simulated { get; private set; } = true;

			public string Failure { get; private set; }

			public GameObject Move(Selectable from, MoveDirection direction)
			{
				//IL_0064: Unknown result type (might be due to invalid IL or missing references)
				//IL_007a: Expected I4, but got Unknown
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0021: Expected O, but got Unknown
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				EventSystem current = EventSystem.current;
				current.SetSelectedGameObject(((Component)from).gameObject);
				if (Simulated)
				{
					try
					{
						AxisEventData val = new AxisEventData(current);
						val.moveDir = direction;
						from.OnMove(val);
						return current.currentSelectedGameObject;
					}
					catch (Exception ex)
					{
						Simulated = false;
						Failure = ex.GetType().Name;
						current.SetSelectedGameObject(((Component)from).gameObject);
					}
				}
				Selectable val2 = (Selectable)((int)direction switch
				{
					1 => from.FindSelectableOnUp(), 
					3 => from.FindSelectableOnDown(), 
					0 => from.FindSelectableOnLeft(), 
					_ => from.FindSelectableOnRight(), 
				});
				if ((Object)(object)val2 != (Object)null && ((UIBehaviour)val2).IsActive())
				{
					current.SetSelectedGameObject(((Component)val2).gameObject);
				}
				return current.currentSelectedGameObject;
			}
		}

		public override string Suite => "Ui";

		public override int Order => 20;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			using UiRowsSandbox sandbox = new UiRowsSandbox("UiNavigation");
			foreach (TestStep item2 in SettingsMenuFixture.SelectMod(context, sandbox.Settings))
			{
				yield return item2;
			}
			Vector3 pointer = new Vector3(-10000f, -10000f, 0f);
			ModsPanel.PointerOverride = () => pointer;
			Mover mover = new Mover();
			try
			{
				EventSystem eventSystem = EventSystem.current;
				Assert.NotNull(eventSystem, "EventSystem.current");
				ModsPanel controller = SettingsMenuFixture.Tab.Controller;
				string language = UiText.LanguageCode;
				string values = Values(sandbox);
				List<Selectable> list = controller.Items.Select((ModListItem val5) => (Selectable)(object)val5.Toggle).ToList();
				int num = controller.Items.ToList().FindIndex((ModListItem val5) => val5.IsSelected);
				Assert.True(num >= 0, "The sandbox mod is selected in the list");
				Selectable item = list[num];
				Assert.True((Object)(object)SettingsMenuFixture.Tab.Tab.FirstSelected != (Object)null && ((Il2CppObjectBase)SettingsMenuFixture.Tab.Tab.FirstSelected).Pointer == ((Il2CppObjectBase)item).Pointer, "Opening the tab with a gamepad starts on the selected mod");
				List<SettingRow> rows = controller.Rows.Where((SettingRow row) => (Object)(object)row.Control != (Object)null).ToList();
				Assert.True(rows.Count >= 3, $"Test setup: the sandbox mod has {rows.Count} rows with controls");
				Button reset = controller.ResetButton;
				Assert.NotNull(reset, "The Mods tab has a reset button");
				if (num < list.Count - 1)
				{
					Expect(mover.Move(item, (MoveDirection)3), list[num + 1], "Down from the selected mod goes to the next mod");
					Expect(mover.Move(list[num + 1], (MoveDirection)1), item, "Up goes back to the selected mod");
				}
				else if (num > 0)
				{
					Expect(mover.Move(item, (MoveDirection)1), list[num - 1], "Up from the selected mod goes to the previous mod");
					Expect(mover.Move(list[num - 1], (MoveDirection)3), item, "Down goes back to the selected mod");
				}
				Expect(mover.Move(item, (MoveDirection)2), rows[0].Control, "Right from the selected mod goes to the first setting");
				yield return Wait.Frames(2);
				ExpectContext(controller, rows[0], language);
				for (int index = 0; index < rows.Count; index++)
				{
					Selectable expected = (Selectable)((index < rows.Count - 1) ? ((object)rows[index + 1].Control) : ((object)reset));
					Expect(mover.Move(rows[index].Control, (MoveDirection)3), expected, "Down from " + rows[index].Setting.Key + " goes to " + ((index < rows.Count - 1) ? rows[index + 1].Setting.Key : "the reset button"));
					if (index < rows.Count - 1)
					{
						yield return Wait.Frames(2);
						ExpectContext(controller, rows[index + 1], language);
						SettingRow obj = rows[index + 1];
						TextRow val = (TextRow)(object)((obj is TextRow) ? obj : null);
						if (val != null)
						{
							Assert.False(val.Input.isFocused, "Selecting " + ((SettingRow)val).Setting.Key + " with a gamepad does not start typing, so the gamepad can move on");
						}
					}
				}
				yield return Wait.Frames(2);
				RectTransform obj2 = ModsTabBuilder.ViewportOf(SettingsMenuFixture.Tab.ContentScroll);
				RectTransform val2 = ((Il2CppObjectBase)rows[rows.Count - 1].Root.transform).TryCast<RectTransform>();
				Rect rect = val2.rect;
				Vector2 val3 = RectTransformUtility.WorldToScreenPoint((Camera)null, ((Transform)val2).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect)).center)));
				Assert.True(RectTransformUtility.RectangleContainsScreenPoint(obj2, val3, (Camera)null), "The pane scrolls with the selection, so the last setting is in view after moving down");
				eventSystem.SetSelectedGameObject(((Component)rows[0].Control).gameObject);
				yield return Wait.Frames(2);
				Assert.True(SettingsMenuFixture.Tab.ContentScroll.verticalNormalizedPosition > 0.99f, "Going back to the first setting scrolls the pane to the top, so the mod card is in view");
				Expect(mover.Move((Selectable)(object)reset, (MoveDirection)1), rows[rows.Count - 1].Control, "Up from the reset button goes to the last setting");
				Expect(mover.Move((Selectable)(object)reset, (MoveDirection)0), item, "Left from the reset button goes back to the selected mod");
				SettingRow val4 = rows.First((SettingRow row) => row is ToggleRow);
				Expect(mover.Move(val4.Control, (MoveDirection)0), item, "Left from " + val4.Setting.Key + " goes back to the selected mod");
				foreach (SettingRow item3 in rows.Where((SettingRow row) => row is SliderRow))
				{
					Selectable selectOnLeft = item3.Control.navigation.selectOnLeft;
					Assert.True((Object)(object)selectOnLeft != (Object)null && ((Il2CppObjectBase)selectOnLeft).Pointer == ((Il2CppObjectBase)item).Pointer, item3.Setting.Key + " links left to the selected mod");
				}
				TextRow textRow = rows.OfType<TextRow>().First();
				eventSystem.SetSelectedGameObject(((Component)((SettingRow)textRow).Control).gameObject);
				bool flag = true;
				try
				{
					textRow.Input.OnSubmit(new BaseEventData(eventSystem));
				}
				catch (Exception ex)
				{
					flag = false;
					context.Note("The text field has no submit handler to call (" + ex.GetType().Name + ")");
				}
				if (flag)
				{
					yield return Wait.Frames(2);
					Assert.True(textRow.Input.isFocused, "Pressing submit on " + ((SettingRow)textRow).Setting.Key + " starts typing");
					textRow.Input.DeactivateInputField(false);
					yield return Wait.Frames(2);
				}
				context.Note("Sliders change their value with left and right, like the game's own sliders");
				context.Note(mover.Simulated ? "Moves were sent to the controls the way the game's input sends them" : ("The game has no move handler to call (" + mover.Failure + "), the links were followed directly"));
				Assert.Equal(values, Values(sandbox), "Moving through the settings changes no values");
			}
			finally
			{
				ModsPanel.PointerOverride = null;
				EventSystem current2 = EventSystem.current;
				if (current2 != null)
				{
					current2.SetSelectedGameObject((GameObject)null);
				}
			}
		}

		private static string Values(UiRowsSandbox sandbox)
		{
			return $"{sandbox.Enabled.Value}|{sandbox.Count.Value}|{sandbox.Speed.Value}|{sandbox.Mode.Value}|{sandbox.Color.Value}|{sandbox.Name.Value}|{sandbox.Seed.Value}|{sandbox.FastMode.Value}|{sandbox.Difficulty.Value}";
		}

		private static void Expect(GameObject actual, Selectable expected, string message)
		{
			Assert.True((Object)(object)expected != (Object)null && UiClone.IsAlive((Object)(object)expected) && ((UIBehaviour)expected).IsActive(), message + ": the target must be active");
			Assert.True((Object)(object)actual != (Object)null && ((Il2CppObjectBase)actual).Pointer == ((Il2CppObjectBase)((Component)expected).gameObject).Pointer, $"{message}, but the selection is {Describe(actual)} instead of {Describe(((Component)expected).gameObject)}");
		}

		private static string Describe(GameObject gameObject)
		{
			if ((Object)(object)gameObject == (Object)null)
			{
				return "empty";
			}
			string text = ((Object)gameObject).name;
			Transform parent = gameObject.transform.parent;
			int num = 0;
			while (num < 2 && (Object)(object)parent != (Object)null)
			{
				text = ((Object)parent).name + "/" + text;
				num++;
				parent = parent.parent;
			}
			return text;
		}

		private static void ExpectContext(ModsPanel controller, SettingRow row, string language)
		{
			Assert.True(row == controller.ContextRow, "The selected " + row.Setting.Key + " drives the description under the settings");
			Assert.Equal(ContextText.For(row.Setting, language), controller.ContextLabel.text, "Description of " + row.Setting.Key);
		}
	}
	public sealed class MenuNoticesTest : TestCase
	{
		public const string Notice = "CatLib notice test";

		public override string Suite => "Ui";

		public override int Order => 19;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			if ((int)SettingsMenuFixture.Context != 0)
			{
				context.Note("Main menu notices can only be checked in the main menu");
				yield break;
			}
			MultiplayerLobbyInterface obj = MenuNotices.Lobby();
			Assert.NotNull(obj, "The multiplayer lobby interface must be found");
			Assert.NotNull(obj.BackButton, "The lobby must have its back button");
			Assert.False(((InterfaceBase)obj).IsShown, "The lobby is not shown in the main menu, so leaving must do nothing");
			Assert.False(MenuNotices.LeaveLobby(), "Leaving is refused while the lobby is hidden");
			TMP_Text val = MenuNotices.LobbyLabel();
			Assert.NotNull(val, "The lobby waiting label must be found");
			string text = val.text;
			MenuNotices.Apply(val, "CatLib notice test");
			string text2 = val.text;
			MenuNotices.Apply(val, "CatLib notice test");
			string text3 = val.text;
			MenuNotices.Apply(val, (string)null);
			context.Note("Lobby label: \"" + text + "\"");
			Assert.True(text2.StartsWith(text) && text2.Contains("CatLib notice test"), "The notice is added under the lobby label");
			Assert.Equal(text2, text3, "Applying the same notice twice changes nothing");
			Assert.Equal(text, val.text, "Clearing restores the lobby label");
			List<ConnectedPlayerHelper> playerSlots = obj._playerSlots;
			context.Note($"Lobby player cards: {playerSlots?.Count ?? 0}");
			if (playerSlots != null && playerSlots.Count > 0)
			{
				TMP_Text playerNameText = playerSlots[0].PlayerNameText;
				Assert.NotNull(playerNameText, "A player card must have a name label");
				string text4 = playerNameText.text;
				MenuNotices.Apply(playerNameText, "CatLib notice test");
				string text5 = playerNameText.text;
				MenuNotices.Apply(playerNameText, (string)null);
				Assert.True(text5.StartsWith(text4) && text5.Contains("CatLib notice test"), "The notice is added under the player name");
				Assert.Equal(text4, playerNameText.text, "Clearing restores the player name");
			}
			MenuNotices.SetPlayerNotice(76561190000000002uL, "CatLib notice test");
			MenuNotices.ApplyToPlayerCards(obj);
			int num = 0;
			int num2 = 0;
			while (playerSlots != null && num2 < playerSlots.Count)
			{
				num += (((Object)(object)playerSlots[num2].PlayerNameText != (Object)null && playerSlots[num2].PlayerNameText.text.Contains("CatLib notice test")) ? 1 : 0);
				num2++;
			}
			MenuNotices.ClearPlayerNotice(76561190000000002uL);
			Assert.Equal(0, num, "A notice for a player who is not in the lobby must not appear on any card");
		}
	}
	public sealed class MenuToSettingTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 7;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			using UiRowsSandbox sandbox = new UiRowsSandbox("UiWrite");
			foreach (TestStep item in SettingsMenuFixture.SelectMod(context, sandbox.Settings))
			{
				yield return item;
			}
			SettingsMenuFixture.Row<ToggleRow>("Enabled").Toggle.isOn = false;
			SettingsMenuFixture.Row<DropdownRow>("Mode").Dropdown.value = 1;
			SettingsMenuFixture.Row<DropdownRow>("Color").Dropdown.value = 2;
			((UnityEvent<string>)(object)SettingsMenuFixture.Row<TextRow>("Name").Input.onEndEdit).Invoke("Tabby");
			((UnityEvent<string>)(object)SettingsMenuFixture.Row<TextRow>("Seed").Input.onEndEdit).Invoke("7");
			((UnityEvent<string>)(object)SettingsMenuFixture.Row<TextRow>("Seed").Input.onEndEdit).Invoke("not a number");
			SliderRow val = SettingsMenuFixture.Row<SliderRow>("Count");
			SliderRow speed = SettingsMenuFixture.Row<SliderRow>("Speed");
			val.Slider.value = 8f;
			speed.Slider.value = 2.3456f;
			Assert.False(sandbox.Enabled.Value, "Toggle must apply immediately");
			Assert.Equal(SampleMode.SlowMode, sandbox.Mode.Value, "Enum dropdown must apply immediately");
			Assert.Equal("Blue", sandbox.Color.Value, "Listed dropdown must apply immediately");
			Assert.Equal("Tabby", sandbox.Name.Value, "Text must apply on end edit");
			Assert.Equal(7, sandbox.Seed.Value, "Number text must apply on end edit");
			Assert.Equal("7", SettingsMenuFixture.Row<TextRow>("Seed").Input.text, "Invalid text must be reverted");
			Assert.Equal(5, sandbox.Count.Value, "Slider must not apply while it is being moved");
			Assert.True(val.HasPendingValue, "Slider must hold a pending value");
			Assert.Equal("8", val.DisplayedText, "Slider text must follow the handle immediately");
			yield return Wait.Seconds(0.45);
			Assert.Equal(8, sandbox.Count.Value, "Slider must apply after the commit delay");
			Assert.Equal(2.35f, sandbox.Speed.Value, "Fractional slider must apply rounded to two digits");
			Assert.Equal("2.35", speed.DisplayedText, "Fractional slider text");
			string text = File.ReadAllText(sandbox.Config.FilePath);
			Assert.True(text.Contains("Enabled = false"), "The toggle change must be saved to the file");
			Assert.True(text.Contains("Count = 8"), "The slider change must be saved to the file");
			Assert.True(text.Contains("Name = Tabby"), "The text change must be saved to the file");
			context.Note("One warning log line about \"not a number\" is expected");
		}
	}
	public sealed class MenuVersionBadgeTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 100;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			if ((int)SettingsMenuFixture.Context != 0 || !Singleton<MainMenuInterfacesManager>.HasInstance())
			{
				context.Note("The version badge can only be checked in the main menu");
				yield break;
			}
			MainMenuInterfacesManager manager = Singleton<MainMenuInterfacesManager>.Instance;
			InterfaceBase menu = manager.MainMenuInterface;
			if (!menu.IsShown)
			{
				manager.ShowMainMenu();
			}
			yield return Wait.Until(() => menu.IsShown && (Object)(object)MenuVersionBadge.Root != (Object)null, 5.0, "the main menu with the version badge");
			yield return Wait.Seconds(1.0);
			GameBuildCheck check = GameCompatibility.Current;
			Assert.NotNull(check, "The game build is checked in the main menu");
			context.Note("Game: " + check.Running.Describe());
			context.Note("Made for: " + check.Target.Describe());
			context.Note($"Status: {check.Status}");
			Assert.NotEqual<GameBuildStatus>((GameBuildStatus)0, check.Status, "The game build is recognised");
			GameObject root = MenuVersionBadge.Root;
			RectTransform rect = ((Il2CppObjectBase)root.transform).TryCast<RectTransform>();
			Canvas rootCanvas = ((Component)menu).GetComponentInParent<Canvas>().rootCanvas;
			Assert.True(((Il2CppObjectBase)root.transform.parent).Pointer == ((Il2CppObjectBase)((Component)rootCanvas).transform).Pointer, "The badge sits on the main menu canvas");
			Assert.True(root.GetComponent<CanvasGroup>().alpha > 0.99f, "The badge is visible while the main menu is shown");
			Assert.True(MenuVersionBadge.Label.text.Contains(VersionBadgeText.Title), "The badge shows the CatLib version");
			Assert.True(MenuVersionBadge.IsPlateShown, "The badge has a plate to stay readable over any background");
			Assert.Equal(check.IsWarning, MenuVersionBadge.IsPlateWarning, "The plate is amber only with a warning");
			RectTransform canvasRect = ((Il2CppObjectBase)((Component)rootCanvas).transform).TryCast<RectTransform>();
			Rect area = canvasRect.rect;
			Rect val = CanvasRect(rect, canvasRect);
			context.Note($"Badge at {((Rect)(ref val)).xMin:0},{((Rect)(ref val)).yMin:0} size {((Rect)(ref val)).width:0}x{((Rect)(ref val)).height:0} on a {((Rect)(ref area)).width:0}x{((Rect)(ref area)).height:0} canvas, lifted by {MenuVersionBadge.Lift:0}");
			Assert.True(((Rect)(ref val)).xMax <= ((Rect)(ref area)).xMax + 0.5f && ((Rect)(ref val)).yMin >= ((Rect)(ref area)).yMin - 0.5f && ((Rect)(ref val)).xMin >= ((Rect)(ref area)).center.x && ((Rect)(ref val)).yMax <= ((Rect)(ref area)).center.y, "The badge is inside the bottom right quarter of the screen");
			foreach (Selectable componentsInChild in ((Component)menu).GetComponentsInChildren<Selectable>(false))
			{
				Rect val2 = CanvasRect(((Il2CppObjectBase)((Component)componentsInChild).transform).TryCast<RectTransform>(), canvasRect);
				if (((Rect)(ref val2)).width < ((Rect)(ref area)).width * 0.5f && ((Rect)(ref val2)).height < ((Rect)(ref area)).height * 0.5f)
				{
					Assert.False(((Rect)(ref val2)).Overlaps(val), "The badge does not cover the " + ((Object)componentsInChild).name + " button");
				}
			}
			string calmText = MenuVersionBadge.Label.text;
			Vector2 calmSize = rect.sizeDelta;
			Camera val3 = (((int)rootCanvas.renderMode == 0) ? null : rootCanvas.worldCamera);
			Rect rect2 = rect.rect;
			Vector2 center = RectTransformUtility.WorldToScreenPoint(val3, ((Transform)rect).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect2)).center)));
			try
			{
				MenuVersionBadge.PointerOverride = () => new Vector3(center.x, center.y, 0f);
				yield return Wait.Frames(3);
				Assert.True(MenuVersionBadge.IsHovered, "Pointing at the badge is noticed");
				Assert.True(MenuVersionBadge.Label.text.Split('\n').Length > calmText.Split('\n').Length, "Pointing at the badge shows the detail line");
				context.Note("Hover: " + MenuVersionBadge.Label.text.Replace("\n", " | "));
				MenuVersionBadge.PointerOverride = () => new Vector3(-10000f, -10000f, 0f);
				yield return Wait.Frames(3);
				Assert.Equal(calmText, MenuVersionBadge.Label.text, "Moving the pointer away hides the detail line");
				string[] array = new string[2] { "CMC 1.02.00.1800.9800.100", "CMC 1.01.00.1700.9722.30000" };
				for (int num = 0; num < array.Length; num++)
				{
					MenuVersionBadge.PreviewCheck = GameCompatibility.Compare(GameBuild.Parse(array[num], 0), GameCompatibility.Supported);
					yield return Wait.Frames(3);
					string value = VersionBadgeText.Warning(MenuVersionBadge.PreviewCheck, UiText.LanguageCode);
					Assert.True(MenuVersionBadge.Label.text.Contains(value), $"The {MenuVersionBadge.PreviewCheck.Status} warning is shown");
					Assert.True(MenuVersionBadge.IsPlateWarning, "A warning has an amber plate");
					Assert.True(rect.sizeDelta.y > calmSize.y || check.IsWarning, "A warning makes the badge taller");
					Rect val4 = CanvasRect(rect, canvasRect);
					Assert.True(((Rect)(ref val4)).xMin >= ((Rect)(ref area)).xMin && ((Rect)(ref val4)).xMax <= ((Rect)(ref area)).xMax + 0.5f, "The warning fits on the screen");
					context.Note($"{MenuVersionBadge.PreviewCheck.Status}: {MenuVersionBadge.Label.text.Replace("\n", " | ")}, {((Rect)(ref val4)).width:0}x{((Rect)(ref val4)).height:0}");
					yield return Wait.Seconds(1.5);
				}
			}
			finally
			{
				MenuVersionBadge.PointerOverride = null;
				MenuVersionBadge.PreviewCheck = null;
			}
			yield return Wait.Frames(3);
			Assert.Equal(calmText, MenuVersionBadge.Label.text, "The badge returns to the real check");
			manager.ShowSettingsMenu();
			yield return Wait.Seconds(1.0);
			Assert.True(root.GetComponent<CanvasGroup>().alpha < 0.01f, "The badge hides with the main menu");
			manager.GoBackToLastInterface();
			yield return Wait.Seconds(1.0);
			Assert.True(root.GetComponent<CanvasGroup>().alpha > 0.99f, "The badge comes back with the main menu");
		}

		private static Rect CanvasRect(RectTransform rect, RectTransform canvasRect)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			Il2CppStructArray<Vector3> val = new Il2CppStructArray<Vector3>(4L);
			rect.GetWorldCorners(val);
			Vector3 val2 = ((Transform)canvasRect).InverseTransformPoint(((Il2CppArrayBase<Vector3>)(object)val)[0]);
			Vector3 val3 = ((Transform)canvasRect).InverseTransformPoint(((Il2CppArrayBase<Vector3>)(object)val)[2]);
			return Rect.MinMaxRect(Mathf.Min(val2.x, val3.x), Mathf.Min(val2.y, val3.y), Mathf.Max(val2.x, val3.x), Mathf.Max(val2.y, val3.y));
		}
	}
	public sealed class ModCardTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 11;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			using UiRowsSandbox sandbox = new UiRowsSandbox("UiCard");
			foreach (TestStep item in SettingsMenuFixture.SelectMod(context, sandbox.Settings))
			{
				yield return item;
			}
			ModCard card = SettingsMenuFixture.Tab.Controller.Card;
			string language = UiText.LanguageCode;
			Assert.Equal(sandbox.Settings.DisplayName, card.Title.text, "Card title");
			Assert.Equal<string>(string.Empty, card.Version.text, "A mod without a version shows no version");
			Assert.Equal(UiText.Plural("SettingsCount", 9, language), card.Status.text, "Card status");
			Assert.Equal(0, card.Root.transform.GetSiblingIndex(), "The card must be the first element of the settings pane");
			sandbox.FastMode.Entry.Value = true;
			yield return Wait.Frames(17);
			string expected = UiText.Plural("SettingsCount", 9, language) + " · " + UiText.Plural("PendingRestart", 1, language);
			context.Note("Status with a pending restart: " + card.Status.text);
			Assert.Equal<string>(expected, card.Status.text, "Card status with a pending restart");
		}
	}
	public sealed class ModIconTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 17;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			foreach (TestStep item in SettingsMenuFixture.EnsureModsTab(context))
			{
				yield return item;
			}
			CatSettings catLib = CatLibRuntime.Settings;
			context.Note("CatLib folder: " + (catLib.PluginDirectory ?? "unknown") + ", icon: " + (catLib.IconPath ?? "none"));
			Assert.NotNull(catLib.IconPath, "The build copies icon.png next to CatLib.dll and CatLib finds it");
			ModListItem val = ((IEnumerable<ModListItem>)SettingsMenuFixture.Tab.Controller.Items).FirstOrDefault((Func<ModListItem, bool>)((ModListItem entry) => entry.Settings == catLib));
			Assert.NotNull(val, "CatLib is in the mods list");
			Assert.True((Object)(object)val.Icon != (Object)null && ((Component)val.Icon).gameObject.activeSelf && (Object)(object)val.Icon.sprite != (Object)null, "CatLib shows its icon in the mods list");
			foreach (ModListItem item2 in SettingsMenuFixture.Tab.Controller.Items)
			{
				Sprite val2 = (((Object)(object)item2.Icon == (Object)null) ? null : item2.Icon.sprite);
				context.Note(item2.Settings.DisplayName + ": " + (((Object)(object)val2 != (Object)null) ? $"icon {((Texture)val2.texture).width}x{((Texture)val2.texture).height}" : "no icon"));
			}
		}
	}
	public sealed class ModsListTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 5;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			foreach (TestStep item in SettingsMenuFixture.EnsureModsTab(context))
			{
				yield return item;
			}
			ModsPanel controller = SettingsMenuFixture.Tab.Controller;
			UiRowsSandbox sandbox = new UiRowsSandbox("UiList");
			try
			{
				yield return Wait.Until(() => SettingsMenuFixture.HasItem(controller, sandbox.Settings), 3.0, "the new mod to appear in the list");
				context.Note($"Mods listed with the sandbox: {controller.Items.Count}");
			}
			finally
			{
				sandbox.Dispose();
			}
			yield return Wait.Until(() => !SettingsMenuFixture.HasItem(controller, sandbox.Settings), 3.0, "the disposed mod to leave the list");
			Assert.True(controller.Selected != null, "A mod must stay selected after the selected one disappears");
		}
	}
	public sealed class ModsTabHygieneTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 1;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			foreach (TestStep item in SettingsMenuFixture.EnsureModsTab(context))
			{
				yield return item;
			}
			ModsTab tab = SettingsMenuFixture.Tab;
			int length = ((Component)tab.Tab).GetComponentsInChildren<Localize>(true).Length;
			int num = 0;
			int num2 = 0;
			foreach (Localize componentsInChild in tab.Panel.GetComponentsInChildren<Localize>(true))
			{
				if ((Object)(object)((Component)componentsInChild).GetComponentInParent<SelectableButton>(true) != (Object)null)
				{
					num2++;
				}
				else
				{
					num++;
				}
			}
			int length2 = tab.Panel.GetComponentsInChildren<LocalizeDropdown>(true).Length;
			int actual = tab.Panel.GetComponentsInChildren<AudioSettingsInterface>(true).Length + tab.Panel.GetComponentsInChildren<GameplaySettingsInterface>(true).Length + tab.Panel.GetComponentsInChildren<ControlsSettingsInterface>(true).Length + tab.Panel.GetComponentsInChildren<InputsSettingsInterface>(true).Length;
			ButtonClickedEvent onClick = ((Component)tab.Tab).GetComponent<Button>().onClick;
			int persistentEventCount = ((UnityEventBase)onClick).GetPersistentEventCount();
			int num3 = 0;
			for (int i = 0; i < persistentEventCount; i++)
			{
				if ((int)((UnityEventBase)onClick).GetPersistentListenerState(i) != 0)
				{
					num3++;
				}
			}
			context.Note($"Tab button persistent listeners: {persistentEventCount}, still enabled: {num3}");
			context.Note($"Localization kept on the revert button: {num2}");
			Assert.Equal(0, length, "Localize components left on the Mods tab");
			Assert.Equal(0, num, "Localize components left in the Mods panel outside the revert button");
			Assert.Equal(0, length2, "LocalizeDropdown components left in the Mods panel");
			Assert.Equal(0, actual, "Game settings interfaces left in the Mods panel");
			Assert.Equal(0, num3, "Enabled persistent listeners on the Mods tab button");
		}
	}
	public sealed class ModsTabInjectedTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 0;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			foreach (TestStep item in SettingsMenuFixture.EnsureModsTab(context))
			{
				yield return item;
			}
			ModsTab tab = SettingsMenuFixture.Tab;
			List<TabInterface> tabs = tab.Options._tabs;
			int num = tabs.IndexOf(tab.Tab);
			TabInterface val = tabs[0];
			context.Note($"Context: {tab.Context}");
			context.Note($"Registered tabs: {tabs.Count}, Mods tab index: {num}");
			Assert.True(num >= 0, "The Mods tab must be registered in OptionsInterface._tabs");
			Assert.Equal(tabs.Count - 1, num, "The Mods tab must be the last registered tab");
			Assert.True(tab.IsVisible, "The Mods tab must be visible while mods declare settings");
			Assert.True(((Il2CppObjectBase)((Component)tab.Tab).transform.parent).Pointer == ((Il2CppObjectBase)((Component)val).transform.parent).Pointer, "The Mods tab must live in the tab bar");
			Assert.True(((Il2CppObjectBase)tab.Panel.transform.parent).Pointer == ((Il2CppObjectBase)val.AssociatedPanel.transform.parent).Pointer, "The Mods panel must live next to the game panels");
			Assert.True(((Il2CppObjectBase)tab.Tab.AssociatedPanel).Pointer == ((Il2CppObjectBase)tab.Panel).Pointer, "The tab must point to the Mods panel");
		}
	}
	public sealed class ModsTabLayoutTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 2;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			foreach (TestStep item in SettingsMenuFixture.EnsureModsTab(context))
			{
				yield return item;
			}
			ModsTab tab = SettingsMenuFixture.Tab;
			RectTransform val = ((Il2CppObjectBase)((Component)tab.ListScroll).transform).TryCast<RectTransform>();
			RectTransform val2 = ((Il2CppObjectBase)((Component)tab.ContentScroll).transform).TryCast<RectTransform>();
			float num = val.anchoredPosition.x + val.sizeDelta.x / 2f;
			float num2 = val2.anchoredPosition.x - val2.sizeDelta.x / 2f;
			context.Note($"List pane width {val.sizeDelta.x}, content pane width {val2.sizeDelta.x}, gap {num2 - num}");
			string languageCode = UiText.LanguageCode;
			string text = ((Component)tab.Tab).GetComponentInChildren<TMP_Text>(true).text;
			string text2 = tab.ListHeader.GetComponentInChildren<TMP_Text>(true).text;
			string text3 = tab.Controller.Card.Title.text;
			context.Note($"Language: {languageCode}, tab \"{text}\", list header \"{text2}\", card \"{text3}\"");
			Assert.True(((Il2CppObjectBase)((Transform)val).parent).Pointer == ((Il2CppObjectBase)tab.Panel.transform).Pointer, "The list pane must be a direct child of the Mods panel");
			Assert.True(((Il2CppObjectBase)((Transform)val2).parent).Pointer == ((Il2CppObjectBase)tab.Panel.transform).Pointer, "The content pane must be a direct child of the Mods panel");
			Assert.True(num2 - num >= 29f, "The panes must not overlap");
			Assert.True(val2.sizeDelta.x > val.sizeDelta.x, "The content pane must be wider than the list pane");
			Assert.Equal(UiText.Get("ModsTab", languageCode), text, "Tab title");
			Assert.Equal(UiText.Get("ModsList", languageCode), text2, "List header");
			CatSettings selected = tab.Controller.Selected;
			Assert.Equal((selected == null) ? UiText.Get("SelectMod", languageCode) : SettingTexts.ModName(selected, languageCode), text3, "Card title");
			Assert.Equal(1 + tab.Controller.Items.Count, ((Transform)tab.ListScroll.content).childCount, "Children of the list pane content");
			Assert.True(tab.Controller.Items.Count >= 1, "The Mods list must contain at least the CatLib.Tests demo mod");
		}
	}
	public sealed class ModsTabSwitchTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 3;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			foreach (TestStep item in SettingsMenuFixture.EnsureModsTab(context))
			{
				yield return item;
			}
			ModsTab modsTab = SettingsMenuFixture.Tab;
			OptionsInterface options = modsTab.Options;
			TabInterface previous = options._selectedTab ?? options._tabs[0];
			modsTab.Tab.SetTabActive(true, true);
			yield return Wait.NextFrame();
			int otherActivePanels = 0;
			List<TabInterface> tabs = options._tabs;
			for (int i = 0; i < tabs.Count; i++)
			{
				TabInterface val = tabs[i];
				if (((Il2CppObjectBase)val).Pointer != ((Il2CppObjectBase)modsTab.Tab).Pointer && (Object)(object)val.AssociatedPanel != (Object)null && val.AssociatedPanel.activeSelf)
				{
					otherActivePanels++;
				}
			}
			bool selectedIsMods = (Object)(object)options._selectedTab != (Object)null && ((Il2CppObjectBase)options._selectedTab).Pointer == ((Il2CppObjectBase)modsTab.Tab).Pointer;
			bool modsPanelActive = modsTab.Panel.activeSelf;
			previous.SetTabActive(true, true);
			yield return Wait.NextFrame();
			bool condition = (Object)(object)options._selectedTab != (Object)null && ((Il2CppObjectBase)options._selectedTab).Pointer == ((Il2CppObjectBase)previous).Pointer;
			bool condition2 = !modsTab.Panel.activeSelf;
			Assert.True(modsPanelActive, "Selecting the Mods tab must show the Mods panel");
			Assert.True(selectedIsMods, "OptionsInterface must treat the Mods tab as selected");
			Assert.Equal(0, otherActivePanels, "Game panels left visible while the Mods tab is selected");
			Assert.True(condition, "Selecting the previous tab again must work");
			Assert.True(condition2, "Leaving the Mods tab must hide the Mods panel");
		}
	}
	public sealed class PlayerMessagesTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 14;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			UiRowsSandbox sandbox = new UiRowsSandbox("UiMessages");
			try
			{
				foreach (TestStep item in SettingsMenuFixture.SelectMod(context, sandbox.Settings))
				{
					yield return item;
				}
				ModsPanel controller = SettingsMenuFixture.Tab.Controller;
				string language = UiText.LanguageCode;
				sandbox.Config.WriteValue("General", "Count", "50");
				yield return Wait.Until(() => sandbox.Config.Adjusted.Count >= 1, 5.0, "the out of range value to be adjusted");
				yield return Wait.Frames(2);
				string expected = UiText.Format("MessageAdjusted", language, new object[4]
				{
					sandbox.Settings.DisplayName,
					"Count",
					"50",
					"10"
				});
				Assert.Equal(expected, PlayerMessages.Last, "Message for an adjusted value");
				Assert.Equal(expected, controller.StatusLabel.text, "The status line must show the latest message");
				sandbox.Config.WriteValue("General", "Count", "many");
				yield return Wait.Until(() => sandbox.Config.Rejected.Count >= 1, 5.0, "the invalid value to be rejected");
				Assert.Equal(UiText.Format("MessageRejected", language, new object[4]
				{
					sandbox.Settings.DisplayName,
					"Count",
					"many",
					"10"
				}), PlayerMessages.Last, "Message for a rejected value");
				sandbox.FastMode.Entry.Value = true;
				yield return Wait.Frames(2);
				Assert.Equal(UiText.Format("MessageRestart", language, new object[2]
				{
					sandbox.Settings.DisplayName,
					"Fast Mode"
				}), PlayerMessages.Last, "Message for a restart-only change");
				context.Note("Warning log lines about \"50\" and \"many\" are expected");
			}
			finally
			{
				if (sandbox != null)
				{
					((IDisposable)sandbox).Dispose();
				}
			}
		}
	}
	internal readonly struct RectExtent
	{
		public float MinX { get; }

		public float MaxX { get; }

		public float MinY { get; }

		public float MaxY { get; }

		public float Width => MaxX - MinX;

		public float Height => MaxY - MinY;

		private RectExtent(float minX, float maxX, float minY, float maxY)
		{
			MinX = minX;
			MaxX = maxX;
			MinY = minY;
			MaxY = maxY;
		}

		public static RectExtent Of(RectTransform target, RectTransform space)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			Rect rect = target.rect;
			float num = float.MaxValue;
			float num2 = float.MinValue;
			float num3 = float.MaxValue;
			float num4 = float.MinValue;
			Vector3[] array = (Vector3[])(object)new Vector3[4]
			{
				new Vector3(((Rect)(ref rect)).xMin, ((Rect)(ref rect)).yMin, 0f),
				new Vector3(((Rect)(ref rect)).xMin, ((Rect)(ref rect)).yMax, 0f),
				new Vector3(((Rect)(ref rect)).xMax, ((Rect)(ref rect)).yMin, 0f),
				new Vector3(((Rect)(ref rect)).xMax, ((Rect)(ref rect)).yMax, 0f)
			};
			foreach (Vector3 val in array)
			{
				Vector3 val2 = ((Transform)space).InverseTransformPoint(((Transform)target).TransformPoint(val));
				num = Mathf.Min(num, val2.x);
				num2 = Mathf.Max(num2, val2.x);
				num3 = Mathf.Min(num3, val2.y);
				num4 = Mathf.Max(num4, val2.y);
			}
			return new RectExtent(num, num2, num3, num4);
		}

		public override string ToString()
		{
			return $"x {MinX:0}..{MaxX:0}, y {MinY:0}..{MaxY:0}";
		}
	}
	public sealed class ResetButtonTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 13;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			using UiRowsSandbox sandbox = new UiRowsSandbox("UiReset");
			foreach (TestStep item in SettingsMenuFixture.SelectMod(context, sandbox.Settings))
			{
				yield return item;
			}
			ModsPanel controller = SettingsMenuFixture.Tab.Controller;
			OptionsInterface options = SettingsMenuFixture.Tab.Options;
			sandbox.Enabled.Entry.Value = false;
			sandbox.Count.Entry.Value = 9;
			sandbox.Mode.Entry.Value = SampleMode.HTTPMode;
			sandbox.Name.Entry.Value = "Tabby";
			sandbox.Secret.Entry.Value = "changed";
			int messages = PlayerMessages.Count;
			Assert.NotNull(controller.ResetButton, "The Mods panel must have a reset button");
			EventSystem current = EventSystem.current;
			if (current != null)
			{
				current.SetSelectedGameObject((GameObject)null);
			}
			((UnityEvent)controller.ResetButton.onClick).Invoke();
			Assert.NotNull(options._actionOnRevertConfirmed, "The reset button must open the game's confirmation popup even when nothing is selected");
			Assert.True(sandbox.Count.Value == 9, "Nothing may change before the reset is confirmed");
			((UnityEvent)options.ConfirmRevertYesButton.onClick).Invoke();
			yield return Wait.Frames(2);
			Assert.True(sandbox.Enabled.Value, "Enabled after the reset");
			Assert.Equal(5, sandbox.Count.Value, "Count after the reset");
			Assert.Equal(SampleMode.FastMode, sandbox.Mode.Value, "Mode after the reset");
			Assert.Equal("Cat", sandbox.Name.Value, "Name after the reset");
			Assert.Equal("changed", sandbox.Secret.Value, "Hidden settings must keep their value");
			Assert.Equal(5f, SettingsMenuFixture.Row<SliderRow>("Count").Slider.value, "The slider must show the default");
			Assert.True(PlayerMessages.Count > messages, "The reset must be reported to the player");
			Assert.Equal(UiText.Format("MessageReset", UiText.LanguageCode, new object[1] { sandbox.Settings.DisplayName }), PlayerMessages.Last, "Reset message");
		}
	}
	public sealed class RestartSuspendTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 17;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			foreach (TestStep item in SettingsMenuFixture.EnsureModsTab(context))
			{
				yield return item;
			}
			MenuContext menu = SettingsMenuFixture.Context;
			foreach (TestStep item2 in SettingsMenuFixture.ShowModsTab(context))
			{
				yield return item2;
			}
			ModsTab before = ModsMenu.Get(menu);
			OptionsInterface options = before.Options;
			ModsMenu.SuspendForRestart();
			yield return Wait.Frames(2);
			Assert.True(ModsMenu.IsSuspended, "The Mods tab must be suspended after a restart starts");
			Assert.Null(ModsMenu.Get(menu), "The Mods tab must be released during a restart");
			yield return Wait.Frames(5);
			Assert.Null(ModsMenu.Get(menu), "Nothing may be injected while the game is restarting");
			ModsMenu.Resume();
			yield return Wait.Until(() => ModsMenu.Get(menu) != null, 5.0, "the Mods tab to be injected again");
			ModsTab val = ModsMenu.Get(menu);
			int num = 0;
			for (int num2 = 0; num2 < options.TabsParent.childCount; num2++)
			{
				num += ((((Object)((Component)options.TabsParent.GetChild(num2)).gameObject).name == "btn_TopTab CatLibMods") ? 1 : 0);
			}
			int num3 = 0;
			for (int num4 = 0; num4 < options._tabs.Count; num4++)
			{
				num3 += ((((Object)((Component)options._tabs[num4]).gameObject).name == "btn_TopTab CatLibMods") ? 1 : 0);
			}
			Transform parent = val.Panel.transform.parent;
			int num5 = 0;
			for (int num6 = 0; num6 < parent.childCount; num6++)
			{
				num5 += ((((Object)((Component)parent.GetChild(num6)).gameObject).name == "panel_CatLibMods") ? 1 : 0);
			}
			int num7 = 0;
			for (int num8 = 0; num8 < ((Il2CppArrayBase<SelectableInputField>)(object)options._inputFields).Length; num8++)
			{
				num7 += ((!UiClone.IsAlive((Object)(object)((Il2CppArrayBase<SelectableInputField>)(object)options._inputFields)[num8])) ? 1 : 0);
			}
			for (int num9 = 0; num9 < ((Il2CppArrayBase<SelectableDropdown>)(object)options._dropdowns).Length; num9++)
			{
				num7 += ((!UiClone.IsAlive((Object)(object)((Il2CppArrayBase<SelectableDropdown>)(object)options._dropdowns)[num9])) ? 1 : 0);
			}
			bool condition = (Object)(object)options._selectedTab == (Object)null || UiClone.IsAlive((Object)(object)options._selectedTab);
			context.Note($"Tabs in the bar: {num}, registered: {num3}, panels: {num5}, dead controls: {num7}");
			Assert.True(condition, "The selected tab of the game menu must not be a destroyed Mods tab");
			Assert.False(before == val, "A new Mods tab must be built after resuming");
			Assert.Equal(1, num, "Mods tabs in the tab bar");
			Assert.Equal(1, num3, "Mods tabs registered in OptionsInterface._tabs");
			Assert.Equal(1, num5, "Mods panels next to the game panels");
			Assert.Equal(0, num7, "Destroyed controls left in the OptionsInterface arrays");
		}
	}
	public sealed class RowGeometryTest : TestCase
	{
		public const float Tolerance = 12f;

		public const float MaxTrackThickness = 60f;

		public override string Suite => "Ui";

		public override int Order => 10;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			using UiRowsSandbox sandbox = new UiRowsSandbox("UiGeometry");
			foreach (TestStep item in SettingsMenuFixture.SelectMod(context, sandbox.Settings))
			{
				yield return item;
			}
			ModsPanel controller = SettingsMenuFixture.Tab.Controller;
			foreach (SettingRow row in controller.Rows)
			{
				Transform obj = row.Root.transform.Find("panel_Label");
				RectTransform val = ((obj != null) ? ((Il2CppObjectBase)obj).TryCast<RectTransform>() : null);
				Transform obj2 = row.Root.transform.Find("panel_Label/img_DashLine");
				RectTransform val2 = ((obj2 != null) ? ((Il2CppObjectBase)obj2).TryCast<RectTransform>() : null);
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null))
				{
					RectExtent value = RectExtent.Of(val2, val);
					Rect rect = val.rect;
					Assert.True(value.MinX >= ((Rect)(ref rect)).xMin - 1f && value.MaxX <= ((Rect)(ref rect)).xMax + 1f, $"Dash line of {row.Setting.Key} ({value}) must stay inside its label panel (x {((Rect)(ref rect)).xMin:0}..{((Rect)(ref rect)).xMax:0})");
				}
			}
			string[] array = new string[2] { "Count", "Speed" };
			Rect rect2;
			foreach (string text in array)
			{
				Slider slider = SettingsMenuFixture.Row<SliderRow>(text).Slider;
				RectTransform val3 = ((Il2CppObjectBase)((Component)slider).transform).TryCast<RectTransform>();
				RectExtent value2 = RectExtent.Of(((Il2CppObjectBase)((Component)slider).transform.Find("img_Bkg")).TryCast<RectTransform>(), val3);
				DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(23, 3);
				defaultInterpolatedStringHandler.AppendFormatted(text);
				defaultInterpolatedStringHandler.AppendLiteral(": slider width ");
				rect2 = val3.rect;
				defaultInterpolatedStringHandler.AppendFormatted(((Rect)(ref rect2)).width, "0");
				defaultInterpolatedStringHandler.AppendLiteral(", track ");
				defaultInterpolatedStringHandler.AppendFormatted(value2);
				context.Note(defaultInterpolatedStringHandler.ToStringAndClear());
				float width = value2.Width;
				rect2 = val3.rect;
				bool condition = Mathf.Abs(width - ((Rect)(ref rect2)).width) <= 12f;
				DefaultInterpolatedStringHandler defaultInterpolatedStringHandler2 = new DefaultInterpolatedStringHandler(47, 3);
				defaultInterpolatedStringHandler2.AppendLiteral("Track of ");
				defaultInterpolatedStringHandler2.AppendFormatted(text);
				defaultInterpolatedStringHandler2.AppendLiteral(" must span the slider: track ");
				defaultInterpolatedStringHandler2.AppendFormatted(value2.Width, "0");
				defaultInterpolatedStringHandler2.AppendLiteral(", slider ");
				rect2 = val3.rect;
				defaultInterpolatedStringHandler2.AppendFormatted(((Rect)(ref rect2)).width, "0");
				Assert.True(condition, defaultInterpolatedStringHandler2.ToStringAndClear());
				Assert.True(value2.Height > 5f && value2.Height <= 60f, $"Track of {text} must have a visible thickness, got {value2.Height:0}");
			}
			foreach (GameObject item2 in new List<GameObject>(controller.SectionHeaders) { SettingsMenuFixture.Tab.ListHeader })
			{
				TMP_Text componentInChildren = item2.GetComponentInChildren<TMP_Text>(true);
				RectTransform val4 = ((Il2CppObjectBase)componentInChildren.transform.parent).TryCast<RectTransform>();
				float x = componentInChildren.GetPreferredValues(componentInChildren.text).x;
				rect2 = val4.rect;
				bool condition2 = x <= ((Rect)(ref rect2)).width;
				DefaultInterpolatedStringHandler defaultInterpolatedStringHandler3 = new DefaultInterpolatedStringHandler(39, 3);
				defaultInterpolatedStringHandler3.AppendLiteral("Tape \"");
				defaultInterpolatedStringHandler3.AppendFormatted(componentInChildren.text);
				defaultInterpolatedStringHandler3.AppendLiteral("\" must fit its text: text ");
				defaultInterpolatedStringHandler3.AppendFormatted(x, "0");
				defaultInterpolatedStringHandler3.AppendLiteral(", tape ");
				rect2 = val4.rect;
				defaultInterpolatedStringHandler3.AppendFormatted(((Rect)(ref rect2)).width, "0");
				Assert.True(condition2, defaultInterpolatedStringHandler3.ToStringAndClear());
			}
		}
	}
	public sealed class SceneOwnershipTest : TestCase
	{
		public const string LegacyStagingName = "CatLib.UiStaging";

		public override string Suite => "Ui";

		public override int Order => 18;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			foreach (TestStep item in SettingsMenuFixture.EnsureModsTab(context))
			{
				yield return item;
			}
			ModsTab tab = SettingsMenuFixture.Tab;
			Scene scene = ((Component)tab.Options).gameObject.scene;
			context.Note("Settings menu scene: " + ((Scene)(ref scene)).name);
			Assert.True(object.Equals(scene, ((Component)tab.Tab).gameObject.scene), "The Mods tab must belong to the scene of its settings menu");
			Assert.True(object.Equals(scene, tab.Panel.scene), "The Mods panel must belong to the scene of its settings menu");
			foreach (SettingRow row in tab.Controller.Rows)
			{
				Assert.True(object.Equals(scene, row.Root.scene), "The row of " + row.Setting.Id + " must belong to the scene of its settings menu");
			}
			int num = 0;
			foreach (GameObject item2 in Resources.FindObjectsOfTypeAll<GameObject>())
			{
				if (((Object)item2).name == "CatLib.Staging" || ((Object)item2).name == "CatLib.UiStaging")
				{
					num++;
				}
			}
			Assert.Equal(0, num, "No staging objects may outlive an injection, they would keep a reference to a scene that gets unloaded");
		}
	}
	public sealed class ScrollbarLayoutTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 16;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			foreach (TestStep item2 in SettingsMenuFixture.EnsureModsTab(context))
			{
				yield return item2;
			}
			ModsTab tab = SettingsMenuFixture.Tab;
			OptionsInterface val = ModsMenu.FindOptions(SettingsMenuFixture.Context);
			List<TabInterface> tabs = val._tabs;
			for (int i = 0; i < tabs.Count; i++)
			{
				GameObject associatedPanel = tabs[i].AssociatedPanel;
				ScrollRect val2 = (((Object)(object)associatedPanel == (Object)null) ? null : ModsTabBuilder.FindDirectScroll(associatedPanel.transform));
				context.Note($"game tab {i}: {ModsTabBuilder.SafeDescribe(((Object)(object)val2 == (Object)null) ? null : val2.verticalScrollbar)}");
			}
			Scrollbar val3 = ModsTabBuilder.FindLaidOutScrollbar(val);
			context.Note("reference: " + ModsTabBuilder.SafeDescribe(val3));
			(string, ScrollRect)[] array = new(string, ScrollRect)[2]
			{
				("list", tab.ListScroll),
				("settings", tab.ContentScroll)
			};
			for (int j = 0; j < array.Length; j++)
			{
				(string, ScrollRect) tuple = array[j];
				string item = tuple.Item1;
				Scrollbar verticalScrollbar = tuple.Item2.verticalScrollbar;
				if ((Object)(object)verticalScrollbar == (Object)null)
				{
					context.Note(item + ": no vertical scrollbar");
					continue;
				}
				RectTransform val4 = ((Il2CppObjectBase)((Component)verticalScrollbar).transform).TryCast<RectTransform>();
				context.Note(item + ": " + ModsTabBuilder.SafeDescribe(verticalScrollbar));
				context.Note($"{item}: scrollbar anchors {val4.anchorMin}-{val4.anchorMax}, size {val4.sizeDelta}");
				Assert.True(val4.anchorMax.y > val4.anchorMin.y, "The " + item + " scrollbar must stretch along the pane, not sit in a corner");
				if ((Object)(object)val3 != (Object)null)
				{
					Image component = ((Component)verticalScrollbar).GetComponent<Image>();
					Image component2 = ((Component)val3).GetComponent<Image>();
					Image val5 = (((Object)(object)verticalScrollbar.handleRect == (Object)null) ? null : ((Component)verticalScrollbar.handleRect).GetComponent<Image>());
					Image val6 = (((Object)(object)val3.handleRect == (Object)null) ? null : ((Component)val3.handleRect).GetComponent<Image>());
					context.Note($"{item}: track {SpriteName(component)} like {SpriteName(component2)}, handle {SpriteName(val5)} like {SpriteName(val6)}");
					Assert.Equal(SpriteName(component2), SpriteName(component), "The " + item + " scrollbar track looks like the game's");
					Assert.Equal(SpriteName(val6), SpriteName(val5), "The " + item + " scrollbar handle looks like the game's");
					Assert.Equal<Color>((Color)(((Object)(object)val6 == (Object)null) ? default(Color) : ((Graphic)val6).color), (Color)(((Object)(object)val5 == (Object)null) ? default(Color) : ((Graphic)val5).color), "The " + item + " scrollbar handle has the game's color");
				}
			}
		}

		private static string SpriteName(Image image)
		{
			if (!((Object)(object)image == (Object)null))
			{
				if (!((Object)(object)image.sprite == (Object)null))
				{
					return ((Object)image.sprite).name;
				}
				return "no sprite";
			}
			return "none";
		}
	}
	public sealed class SessionOverrideRowTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 15;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			using UiRowsSandbox sandbox = new UiRowsSandbox("UiOverride");
			foreach (TestStep item in SettingsMenuFixture.SelectMod(context, sandbox.Settings))
			{
				yield return item;
			}
			string language = UiText.LanguageCode;
			SliderRow row = SettingsMenuFixture.Row<SliderRow>("Difficulty");
			try
			{
				SessionApplyResult val = SessionSettings.Instance.Apply((IReadOnlyList<SessionSettingValue>)(object)new SessionSettingValue[1]
				{
					new SessionSettingValue(sandbox.Settings.OwnerId, "Session", "Difficulty", "4")
				});
				Assert.Equal(1, val.Applied, "Override applied");
				EventSystem current = EventSystem.current;
				if (current != null)
				{
					current.SetSelectedGameObject(((Component)((SettingRow)row).Control).gameObject);
				}
				yield return Wait.Frames(3);
				ModsPanel controller = SettingsMenuFixture.Tab.Controller;
				EventSystem current2 = EventSystem.current;
				GameObject val2 = ((current2 != null) ? current2.currentSelectedGameObject : null);
				context.Note("Label: " + ((SettingRow)row).Label.text + ", context: " + controller.ContextLabel.text.Replace("\n", " | "));
				Assert.Equal(4f, row.Slider.value, "The row must show the host value");
				Assert.True((Object)(object)val2 != (Object)null && ((Il2CppObjectBase)val2).Pointer == ((Il2CppObjectBase)((Component)((SettingRow)row).Control).gameObject).Pointer, "An overridden row must stay selectable for gamepad players");
				Assert.True(((SettingRow)row).Control.interactable, "An overridden row must stay in the navigation");
				Assert.Equal(0.5f, ((SettingRow)row).ValueGroup.alpha, "An overridden value must be dimmed");
				Assert.False(((SettingRow)row).ValueGroup.blocksRaycasts, "An overridden value must ignore the mouse");
				Assert.Equal<string>("Difficulty " + UiText.Get("HostSuffix", language), ((SettingRow)row).Label.text, "Host suffix in the label");
				Assert.True(controller.ContextLabel.text.Contains(UiText.Format("HostValue", language, new object[1] { "2" })), "The context must show the player's own value");
				row.Slider.value = 5f;
				Assert.Equal(4f, row.Slider.value, "Moving an overridden slider must snap back to the host value");
				yield return Wait.Seconds(0.45);
				Assert.Equal(2, sandbox.Difficulty.LocalValue, "An overridden row must never write the local value");
				Assert.Equal(4, sandbox.Difficulty.Value, "The host value stays in effect");
				SessionSettings.Instance.Clear();
				yield return Wait.Frames(3);
				Assert.Equal(2f, row.Slider.value, "The row must show the local value after the session");
				Assert.Equal(1f, ((SettingRow)row).ValueGroup.alpha, "The value must not be dimmed after the session");
				Assert.True(((SettingRow)row).ValueGroup.blocksRaycasts, "The value must accept the mouse after the session");
				Assert.Equal("Difficulty", ((SettingRow)row).Label.text, "Label without the host suffix");
			}
			finally
			{
				SessionSettings.Instance.Clear();
				EventSystem current3 = EventSystem.current;
				if (current3 != null)
				{
					current3.SetSelectedGameObject((GameObject)null);
				}
			}
		}
	}
	public sealed class SettingRowsTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 6;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			using UiRowsSandbox sandbox = new UiRowsSandbox("UiRows");
			foreach (TestStep item in SettingsMenuFixture.SelectMod(context, sandbox.Settings))
			{
				yield return item;
			}
			ModsPanel controller = SettingsMenuFixture.Tab.Controller;
			IReadOnlyList<SettingRow> rows = controller.Rows;
			string languageCode = UiText.LanguageCode;
			context.Note("Rows: " + string.Join(", ", rows.Select((SettingRow row) => row.Setting.Key + "=" + ((object)row.Kind/*cast due to .constrained prefix*/).ToString())));
			Assert.True(sandbox.Settings == controller.Selected, "The sandbox mod must be selected");
			Assert.Equal(9, rows.Count, "Rows for visible settings");
			Assert.Equal(5, controller.SectionHeaders.Count, "Section headers");
			Assert.False(rows.Any((SettingRow row) => row.Setting.Key == "Secret"), "Hidden settings must not get a row");
			Assert.Equal<ControlKind>((ControlKind)0, ((SettingRow)SettingsMenuFixture.Row<ToggleRow>("Enabled")).Kind, "Enabled");
			Assert.Equal<ControlKind>((ControlKind)1, ((SettingRow)SettingsMenuFixture.Row<SliderRow>("Count")).Kind, "Count");
			Assert.Equal<ControlKind>((ControlKind)1, ((SettingRow)SettingsMenuFixture.Row<SliderRow>("Speed")).Kind, "Speed");
			Assert.Equal<ControlKind>((ControlKind)2, ((SettingRow)SettingsMenuFixture.Row<DropdownRow>("Mode")).Kind, "Mode");
			Assert.Equal<ControlKind>((ControlKind)2, ((SettingRow)SettingsMenuFixture.Row<DropdownRow>("Color")).Kind, "Color");
			Assert.Equal<ControlKind>((ControlKind)3, ((SettingRow)SettingsMenuFixture.Row<TextRow>("Name")).Kind, "Name");
			Assert.Equal<ControlKind>((ControlKind)3, ((SettingRow)SettingsMenuFixture.Row<TextRow>("Seed")).Kind, "Seed");
			Assert.Equal("Custom label", ((SettingRow)SettingsMenuFixture.Row<TextRow>("Name")).Label.text, "Custom label");
			Assert.Equal("Seed", ((SettingRow)SettingsMenuFixture.Row<TextRow>("Seed")).Label.text, "Prettified label");
			Assert.Equal<string>("Fast Mode " + UiText.Get("RestartSuffix", languageCode), ((SettingRow)SettingsMenuFixture.Row<ToggleRow>("FastMode")).Label.text, "Restart suffix");
			Assert.Equal(3, SettingsMenuFixture.Row<DropdownRow>("Color").Dropdown.options.Count, "Color options");
			Assert.Equal("5", SettingsMenuFixture.Row<SliderRow>("Count").DisplayedText, "Count display");
			Assert.Equal("1.5", SettingsMenuFixture.Row<SliderRow>("Speed").DisplayedText, "Speed display");
			Assert.Equal("Cat", SettingsMenuFixture.Row<TextRow>("Name").Input.text, "Name text");
		}
	}
	public sealed class SettingsMenuCleanupTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 99;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			SettingsMenuFixture.RestoreTab();
			SettingsMenuFixture.CloseIfOpened(context);
			yield break;
		}
	}
	internal static class SettingsMenuFixture
	{
		public static bool OpenedByTests { get; private set; }

		public static TabInterface PreviousTab { get; private set; }

		public static MenuContext Context
		{
			get
			{
				if (Singleton<InterfaceManager>.HasInstance())
				{
					return (MenuContext)1;
				}
				return (MenuContext)0;
			}
		}

		public static ModsTab Tab => ModsMenu.Get(Context);

		public static IEnumerable<TestStep> EnsureModsTab(TestContext context)
		{
			if (Tab == null)
			{
				if ((int)Context == 0 && Singleton<MainMenuInterfacesManager>.HasInstance())
				{
					context.Note("The settings menu was not initialized yet, opening it");
					Singleton<MainMenuInterfacesManager>.Instance.ShowSettingsMenu();
					OpenedByTests = true;
				}
				else
				{
					context.Note("Open the settings menu from the pause menu before running UI tests in a level");
				}
				yield return Wait.Until(() => Tab != null, 10.0, "the Mods tab to be injected into the " + ((object)Context/*cast due to .constrained prefix*/).ToString() + " settings menu");
			}
		}

		public static IEnumerable<TestStep> ShowModsTab(TestContext context)
		{
			foreach (TestStep item in EnsureModsTab(context))
			{
				yield return item;
			}
			OptionsInterface options = Tab.Options;
			if ((Object)(object)options._selectedTab == (Object)null || ((Il2CppObjectBase)options._selectedTab).Pointer != ((Il2CppObjectBase)Tab.Tab).Pointer)
			{
				if (PreviousTab == null)
				{
					PreviousTab = options._selectedTab ?? options._tabs[0];
				}
				Tab.Tab.SetTabActive(true, true);
				yield return Wait.NextFrame();
			}
		}

		public static IEnumerable<TestStep> SelectMod(TestContext context, CatSettings settings)
		{
			foreach (TestStep item in ShowModsTab(context))
			{
				yield return item;
			}
			ModsPanel controller = Tab.Controller;
			yield return Wait.Until(() => HasItem(controller, settings), 3.0, "the mod to appear in the Mods list");
			controller.Select(settings);
			yield return Wait.NextFrame();
		}

		public static bool HasItem(ModsPanel controller, CatSettings settings)
		{
			foreach (ModListItem item in controller.Items)
			{
				if (item.Settings == settings)
				{
					return true;
				}
			}
			return false;
		}

		public static T Row<T>(string key) where T : SettingRow
		{
			foreach (SettingRow row in Tab.Controller.Rows)
			{
				if (row.Setting.Key == key)
				{
					T val = (T)(object)((row is T) ? row : null);
					if (val != null)
					{
						return val;
					}
				}
			}
			Assert.Fail($"No {typeof(T).Name} for {key} in the Mods panel");
			return default(T);
		}

		public static void RestoreTab()
		{
			if ((Object)(object)PreviousTab != (Object)null && UiClone.IsAlive((Object)(object)PreviousTab))
			{
				PreviousTab.SetTabActive(true, true);
			}
			PreviousTab = null;
		}

		public static void CloseIfOpened(TestContext context)
		{
			if (OpenedByTests)
			{
				OpenedByTests = false;
				if (Singleton<MainMenuInterfacesManager>.HasInstance())
				{
					Singleton<MainMenuInterfacesManager>.Instance.GoBackToLastInterface();
					context.Note("Closed the settings menu opened by the tests");
				}
			}
		}
	}
	public sealed class SettingToMenuTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 8;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			UiRowsSandbox sandbox = new UiRowsSandbox("UiRead");
			try
			{
				foreach (TestStep item in SettingsMenuFixture.SelectMod(context, sandbox.Settings))
				{
					yield return item;
				}
				string text = sandbox.Config.ContentWith("General", "Enabled", "false");
				text = ConfigSandbox.ReplaceValue(text, "General", "Count", "3");
				text = ConfigSandbox.ReplaceValue(text, "Mode", "Color", "Green");
				text = ConfigSandbox.ReplaceValue(text, "Text", "Name", "Edited");
				File.WriteAllText(sandbox.Config.FilePath, text);
				yield return Wait.Until(() => sandbox.Config.Reports.Count >= 1, 5.0, "the edited file to be reloaded");
				yield return Wait.Frames(2);
				Assert.False(SettingsMenuFixture.Row<ToggleRow>("Enabled").Toggle.isOn, "Toggle after a file edit");
				Assert.Equal(3f, SettingsMenuFixture.Row<SliderRow>("Count").Slider.value, "Slider after a file edit");
				Assert.Equal("3", SettingsMenuFixture.Row<SliderRow>("Count").DisplayedText, "Slider text after a file edit");
				Assert.Equal(1, SettingsMenuFixture.Row<DropdownRow>("Color").Dropdown.value, "Dropdown after a file edit");
				Assert.Equal("Edited", SettingsMenuFixture.Row<TextRow>("Name").Input.text, "Text after a file edit");
				sandbox.Count.Entry.Value = 9;
				yield return Wait.Frames(2);
				Assert.Equal(9f, SettingsMenuFixture.Row<SliderRow>("Count").Slider.value, "Slider after a change from code");
			}
			finally
			{
				if (sandbox != null)
				{
					((IDisposable)sandbox).Dispose();
				}
			}
		}
	}
	public sealed class TabBarFitTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 4;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			foreach (TestStep item in SettingsMenuFixture.EnsureModsTab(context))
			{
				yield return item;
			}
			ModsTab modsTab = SettingsMenuFixture.Tab;
			yield return Wait.Until(() => modsTab.Fitter.IsFitted, 5.0, "the tab bar to be fitted");
			Rect rect = ((Il2CppObjectBase)((Component)modsTab.Options).transform).TryCast<RectTransform>().rect;
			float num = default(float);
			float num2 = default(float);
			bool condition = modsTab.Fitter.Measure(ref num, ref num2);
			context.Note($"Screen {Screen.width}x{Screen.height}, canvas {((Rect)(ref rect)).width:0}x{((Rect)(ref rect)).height:0}");
			context.Note($"Tab bar spans {num:0}..{num2:0} of {((Rect)(ref rect)).xMin:0}..{((Rect)(ref rect)).xMax:0}, scale {modsTab.Fitter.Scale:0.###}");
			Assert.True(condition, "The tab bar must have measurable children");
			Assert.True(num >= ((Rect)(ref rect)).xMin, "The left edge of the tab bar must stay on screen");
			Assert.True(num2 <= ((Rect)(ref rect)).xMax - 20f + 1f, "The right edge of the tab bar, including the page hint, must stay on screen");
		}
	}
	internal sealed class UiRowsSandbox : IDisposable
	{
		public const int VisibleCount = 9;

		public const int SectionCount = 5;

		public ConfigSandbox Config { get; }

		public CatSettings Settings => Config.Settings;

		public Setting<bool> Enabled { get; }

		public Setting<int> Count { get; }

		public Setting<float> Speed { get; }

		public Setting<SampleMode> Mode { get; }

		public Setting<string> Color { get; }

		public Setting<string> Name { get; }

		public Setting<int> Seed { get; }

		public Setting<string> Secret { get; }

		public Setting<bool> FastMode { get; }

		public Setting<int> Difficulty { get; }

		public UiRowsSandbox(string name)
		{
			Config = new ConfigSandbox(name);
			CatSettings settings = Config.Settings;
			Enabled = settings.Local<bool>("General", "Enabled", true, "Enabled.", (AcceptableValueBase)null);
			Count = settings.Local<int>("General", "Count", 5, "Count.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10));
			Speed = settings.Local<float>("General", "Speed", 1.5f, "Speed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f));
			Mode = settings.Local<SampleMode>("Mode", "Mode", SampleMode.FastMode, "Mode.", (AcceptableValueBase)null);
			Color = settings.Local<string>("Mode", "Color", "Red", "Color.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Red", "Green", "Blue" }));
			Name = settings.Local<string>("Text", "Name", "Cat", "Name.", (AcceptableValueBase)null).Label("Custom label");
			Seed = settings.Local<int>("Text", "Seed", 42, "Seed.", (AcceptableValueBase)null);
			Secret = settings.Local<string>("Text", "Secret", "hidden", "Secret.", (AcceptableValueBase)null).HiddenInMenu();
			FastMode = settings.Local<bool>("Advanced", "FastMode", false, "Fast mode.", (AcceptableValueBase)null).RequiresRestart();
			Difficulty = settings.Session<int>("Session", "Difficulty", 2, "Difficulty.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 5));
		}

		public void Dispose()
		{
			Config.Dispose();
		}
	}
	public sealed class ViewportMaskTest : TestCase
	{
		public override string Suite => "Ui";

		public override int Order => 16;

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			foreach (TestStep item3 in SettingsMenuFixture.EnsureModsTab(context))
			{
				yield return item3;
			}
			ModsTab tab = SettingsMenuFixture.Tab;
			(string, ScrollRect)[] array = new(string, ScrollRect)[2]
			{
				("list", tab.ListScroll),
				("settings", tab.ContentScroll)
			};
			for (int i = 0; i < array.Length; i++)
			{
				(string, ScrollRect) tuple = array[i];
				string item = tuple.Item1;
				ScrollRect item2 = tuple.Item2;
				RectTransform val = ModsTabBuilder.ViewportOf(item2);
				Assert.NotNull(val, "Viewport of the " + item + " pane");
				Mask component = ((Component)val).GetComponent<Mask>();
				Image component2 = ((Component)val).GetComponent<Image>();
				context.Note($"{item}: mask {(((Object)(object)component == (Object)null) ? "missing" : (((Behaviour)component).enabled ? "enabled" : "disabled"))}, graphic {(((Object)(object)component2 == (Object)null) ? "missing" : (((Behaviour)component2).enabled ? "enabled" : "disabled"))}");
				Assert.True((Object)(object)component != (Object)null && ((Behaviour)component).enabled, "The " + item + " pane must have an enabled mask");
				Assert.True((Object)(object)component2 != (Object)null && ((Behaviour)component2).enabled, "The mask graphic of the " + item + " pane must be enabled, otherwise nothing is clipped");
				Assert.True(((Transform)item2.content).IsChildOf((Transform)(object)val), "The " + item + " content must be inside the masked viewport");
			}
		}
	}
}
namespace CatLib.Tests.Suites.ShelfLabels
{
	public sealed class LabelBoardTest : TestCase
	{
		public override string Suite => "ShelfLabels";

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			LabelSlot val = default(LabelSlot);
			Assert.True(LabelSlot.TryParse("390/2", ref val) && val == new LabelSlot(390, 2), "Slot text");
			LabelSlot val2 = default(LabelSlot);
			Assert.False(LabelSlot.TryParse("390/4", ref val2), "At most three extra labels");
			Assert.False(LabelSlot.TryParse("390/0", ref val2), "Slot 0 is the game's own label and is never addressed");
			Assert.False(LabelSlot.TryParse("-1/1", ref val2), "Negative ids are refused");
			Assert.False(LabelSlot.TryParse("390/1/2", ref val2), "Extra parts are refused");
			Assert.Equal("label/390/2", ((LabelSlot)(ref val)).SaveKey, "Save key");
			LabelSlot val3 = default(LabelSlot);
			Assert.True(LabelSlot.TryParseSaveKey("label/390/2", ref val3) && val3 == val, "Save key round trip");
			Assert.False(LabelSlot.TryParseSaveKey("other/390/2", ref val2), "Foreign keys are skipped");
			LabelBoard val4 = new LabelBoard();
			List<IReadOnlyList<LabelSlot>> list = new List<IReadOnlyList<LabelSlot>>();
			val4.Changed += list.Add;
			Assert.Equal(0, val4.Get(val), "An unknown slot shows the empty picture");
			Assert.Equal(1, val4.Step(val, 1, 10), "Forward");
			Assert.Equal(0, val4.Step(val, -1, 10), "Back");
			Assert.Equal(9, val4.Step(val, -1, 10), "Back from the empty picture wraps to the last one");
			Assert.Equal(0, val4.Step(val, 1, 10), "Forward from the last one wraps to the empty picture");
			Assert.Equal(-1, val4.Step(val, 1, 0), "Unknown picture count changes nothing");
			Assert.Equal(4, list.Count, "Every change is reported");
			Assert.False(val4.Set(val, 0), "Setting the same value is not a change");
			Assert.False(val4.Set(val, 300), "Out of range values are refused");
			Assert.True(val4.Has(val), "A slot set back to empty is still stored");
			val4.Set(new LabelSlot(12, 3), 7);
			val4.Set(new LabelSlot(390, 1), 2);
			string text = LabelBoard.Format((IEnumerable<KeyValuePair<LabelSlot, int>>)val4.Entries);
			Assert.Equal("12/3=7;390/1=2;390/2=0", text, "Format is sorted");
			List<KeyValuePair<LabelSlot, int>> list2 = LabelBoard.Parse(text + ";garbage;390/9=1;5/1=x;5/1=999");
			Assert.Equal(3, list2.Count, "Malformed entries are skipped");
			LabelBoard val5 = new LabelBoard();
			val5.Apply(Enumerable.Range(1, 900).SelectMany((int id) => from number in Enumerable.Range(1, 3)
				select new KeyValuePair<LabelSlot, int>(new LabelSlot(id, number), id % 10)));
			List<string> list3 = val5.Chunks(12288);
			Assert.True(list3.Count > 1, "A large board is split into several messages");
			Assert.True(list3.All((string chunk) => Encoding.UTF8.GetByteCount(chunk) <= 12288), "Every chunk fits");
			Assert.Equal(2700, list3.Sum((string chunk) => LabelBoard.Parse(chunk).Count), "No entry is lost");
			val4.Clear();
			Assert.Equal(0, val4.Count, "Clear");
			yield break;
		}
	}
	public sealed class LabelSaveTest : TestCase
	{
		public override string Suite => "ShelfLabels";

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			using (SaveSandbox saveSandbox = new SaveSandbox("ShelfLabels"))
			{
				LabelBoard val = new LabelBoard();
				PlacementBoard val2 = new PlacementBoard();
				StandBoard val3 = new StandBoard();
				new LabelStore(saveSandbox.Session.Register("catlib.shelflabels", "0.1.0", 1), val, val2, val3, (CatLogger)null);
				saveSandbox.Session.Select("GameSave_20260101_120000_Cat-Mail-Co.bin", (bool?)false);
				val3.Set(390, true);
				val3.Set(12, false);
				val2.Set(390, (Placement)4);
				val2.Set(12, (Placement)3);
				val.Set(new LabelSlot(390, 1), 2);
				val.Set(new LabelSlot(390, 2), 5);
				val.Set(new LabelSlot(390, 3), 8);
				saveSandbox.Save();
				Assert.True(File.ReadAllText(saveSandbox.PathOf("catlib.shelflabels")).Contains("label/390/3"), "Pictures are written with the game save");
				LabelBoard val4 = new LabelBoard();
				PlacementBoard val5 = new PlacementBoard();
				StandBoard val6 = new StandBoard();
				new LabelStore(saveSandbox.Restart().Register("catlib.shelflabels", "0.1.0", 1), val4, val5, val6, (CatLogger)null);
				saveSandbox.Session.Select("GameSave_20260101_120000_Cat-Mail-Co.bin", (bool?)false);
				Assert.Equal(8, val4.Get(new LabelSlot(390, 3)), "Pictures come back after a restart");
				Assert.Equal<Placement>((Placement)4, val5.Get(390, (Placement)0), "A shelf without copies stays without copies");
				Assert.Equal<Placement>((Placement)3, val5.Get(12, (Placement)0), "Placements come back after a restart");
				Assert.True(val6.IsHidden(390, false), "A hidden stand stays hidden");
				Assert.False(val6.IsHidden(12, true), "An explicitly shown stand stays shown even when the default hides stands");
				Assert.Equal(8, val4.Get(new LabelSlot(390, 3)), "Pictures of a shelf without copies are still kept");
				Assert.Equal(3, val4.Count, "All of them");
				val4.Set(new LabelSlot(390, 1), 3);
				saveSandbox.Save();
				LabelBoard val7 = new LabelBoard();
				PlacementBoard val8 = new PlacementBoard();
				new LabelStore(saveSandbox.Restart().Register("catlib.shelflabels", "0.1.0", 1), val7, val8, new StandBoard(), (CatLogger)null);
				saveSandbox.Session.Select("GameSave_20260101_120000_Cat-Mail-Co.bin", (bool?)false);
				Assert.Equal(5, val7.Get(new LabelSlot(390, 2)), "Slots hidden in the meantime keep their pictures across saves");
				Assert.Equal(8, val7.Get(new LabelSlot(390, 3)), "and come back when they are shown again");
				Assert.Equal(3, val7.Get(new LabelSlot(390, 1)), "while the visible slot has its new picture");
				saveSandbox.Session.Select("GameSave_Other.bin", (bool?)false);
				Assert.Equal(0, val7.Count, "Another save starts without these pictures");
				Assert.Equal(0, val8.Count, "and without these placements");
			}
			yield break;
		}
	}
	public sealed class LabelSyncTest : TestCase
	{
		public override string Suite => "ShelfLabels";

		public override IEnumerable<TestStep> Run(TestContext context)
		{
			ModInfo val = NetworkFixture.Mod("catlib.shelflabels", "0.1.0", (SessionPolicy)1, (VersionRule)2);
			ModMessagingFixture modMessagingFixture = new ModMessagingFixture(NetworkFixture.Identity(val), NetworkFixture.Identity(val));
			LabelBoard val2 = new LabelBoard();
			LabelBoard val3 = new LabelBoard();
			PlacementBoard val4 = new PlacementBoard();
			PlacementBoard val5 = new PlacementBoard();
			StandBoard val6 = new StandBoard();
			StandBoard val7 = new StandBoard();
			bool hideStands = false;
			int slots = 2;
			Placement defaultPlacement = (Placement)0;
			LabelSync val8 = new LabelSync(modMessagingFixture.HostMessenger.Channel("catlib.shelflabels"), val2, val4, val6, (Func<int, int>)((int id) => (id == 390 || id == 12) ? 10 : 0), (Func<int>)(() => slots), (Func<Placement>)(() => defaultPlacement), (Func<bool>)(() => hideStands), (CatLogger)null);
			LabelSync val9 = new LabelSync(modMessagingFix