Без опису
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. using System;
  2. using System.Collections;
  3. using System.Reflection;
  4. using System.IO;
  5. using NUnit.Framework;
  6. using UnityEditor;
  7. using UnityEngine;
  8. using UnityEngine.UI;
  9. using UnityEngine.EventSystems;
  10. using UnityEngine.TestTools;
  11. using System.Runtime.CompilerServices;
  12. public class ScrollRectTests : IPrebuildSetup
  13. {
  14. const int ScrollSensitivity = 3;
  15. GameObject m_PrefabRoot;
  16. const string kPrefabPath = "Assets/Resources/ScrollRectPrefab.prefab";
  17. public void Setup()
  18. {
  19. #if UNITY_EDITOR
  20. var rootGO = new GameObject("rootGo");
  21. GameObject eventSystemGO = new GameObject("EventSystem", typeof(EventSystem));
  22. eventSystemGO.transform.SetParent(rootGO.transform);
  23. var canvasGO = new GameObject("Canvas", typeof(Canvas));
  24. canvasGO.transform.SetParent(rootGO.transform);
  25. var canvas = canvasGO.GetComponent<Canvas>();
  26. canvas.referencePixelsPerUnit = 100;
  27. GameObject scrollRectGO = new GameObject("ScrollRect", typeof(RectTransform), typeof(ScrollRect));
  28. scrollRectGO.transform.SetParent(canvasGO.transform);
  29. GameObject contentGO = new GameObject("Content", typeof(RectTransform));
  30. contentGO.transform.SetParent(scrollRectGO.transform);
  31. GameObject horizontalScrollBarGO = new GameObject("HorizontalScrollBar", typeof(Scrollbar));
  32. horizontalScrollBarGO.transform.SetParent(scrollRectGO.transform);
  33. GameObject verticalScrollBarGO = new GameObject("VerticalScrollBar", typeof(Scrollbar));
  34. verticalScrollBarGO.transform.SetParent(scrollRectGO.transform);
  35. ScrollRect scrollRect = scrollRectGO.GetComponent<ScrollRect>();
  36. scrollRect.transform.position = Vector3.zero;
  37. scrollRect.transform.rotation = Quaternion.identity;
  38. scrollRect.transform.localScale = Vector3.one;
  39. (scrollRect.transform as RectTransform).sizeDelta = new Vector3(0.5f, 0.5f);
  40. scrollRect.horizontalScrollbar = horizontalScrollBarGO.GetComponent<Scrollbar>();
  41. scrollRect.verticalScrollbar = verticalScrollBarGO.GetComponent<Scrollbar>();
  42. scrollRect.content = contentGO.GetComponent<RectTransform>();
  43. scrollRect.content.anchoredPosition = Vector2.zero;
  44. scrollRect.content.sizeDelta = new Vector2(3, 3);
  45. scrollRect.scrollSensitivity = ScrollSensitivity;
  46. scrollRect.GetComponent<RectTransform>().sizeDelta = new Vector2(1, 1);
  47. if (!Directory.Exists("Assets/Resources/"))
  48. Directory.CreateDirectory("Assets/Resources/");
  49. PrefabUtility.SaveAsPrefabAsset(rootGO, kPrefabPath);
  50. GameObject.DestroyImmediate(rootGO);
  51. #endif
  52. }
  53. [SetUp]
  54. public void TestSetup()
  55. {
  56. m_PrefabRoot = UnityEngine.Object.Instantiate(Resources.Load("ScrollRectPrefab")) as GameObject;
  57. }
  58. [TearDown]
  59. public void TearDown()
  60. {
  61. GameObject.DestroyImmediate(m_PrefabRoot);
  62. }
  63. [OneTimeTearDown]
  64. public void OneTimeTearDown()
  65. {
  66. #if UNITY_EDITOR
  67. AssetDatabase.DeleteAsset(kPrefabPath);
  68. #endif
  69. }
  70. #region Enable disable scrollbars
  71. [UnityTest, Ignore("Disabled for Instability https://jira.unity3d.com/browse/UUM-42513")]
  72. [TestCase(true, ExpectedResult = null)]
  73. [TestCase(false, ExpectedResult = null)]
  74. public IEnumerator OnEnableShouldAddListeners(bool isHorizontal)
  75. {
  76. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  77. Scrollbar scrollbar = isHorizontal ? scrollRect.horizontalScrollbar : scrollRect.verticalScrollbar;
  78. scrollRect.enabled = false;
  79. yield return null;
  80. FieldInfo field = scrollbar.onValueChanged.GetType().BaseType.BaseType.GetField("m_Calls", BindingFlags.NonPublic | BindingFlags.Instance);
  81. object invokeableCallList = field.GetValue(scrollbar.onValueChanged);
  82. PropertyInfo property = invokeableCallList.GetType().GetProperty("Count", BindingFlags.Public | BindingFlags.Instance);
  83. int callCount = (int)property.GetValue(invokeableCallList, null);
  84. scrollRect.enabled = true;
  85. yield return null;
  86. Assert.AreEqual(callCount + 1, (int)property.GetValue(invokeableCallList, null));
  87. }
  88. [UnityTest, Ignore("Disabled for Instability https://jira.unity3d.com/browse/UUM-42513")]
  89. [TestCase(true, ExpectedResult = null)]
  90. [TestCase(false, ExpectedResult = null)]
  91. public IEnumerator OnDisableShouldRemoveListeners(bool isHorizontal)
  92. {
  93. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  94. Scrollbar scrollbar = isHorizontal ? scrollRect.horizontalScrollbar : scrollRect.verticalScrollbar;
  95. scrollRect.enabled = true;
  96. yield return null;
  97. FieldInfo field = scrollbar.onValueChanged.GetType().BaseType.BaseType.GetField("m_Calls", BindingFlags.NonPublic | BindingFlags.Instance);
  98. object invokeableCallList = field.GetValue(scrollbar.onValueChanged);
  99. PropertyInfo property = invokeableCallList.GetType().GetProperty("Count", BindingFlags.Public | BindingFlags.Instance);
  100. Assert.AreNotEqual(0, (int)property.GetValue(invokeableCallList, null));
  101. scrollRect.enabled = false;
  102. yield return null;
  103. Assert.AreEqual(0, (int)property.GetValue(invokeableCallList, null));
  104. }
  105. [Test, Ignore("Disabled for Instability https://jira.unity3d.com/browse/UUM-42513")]
  106. [TestCase(true)]
  107. [TestCase(false)]
  108. public void SettingScrollbarShouldRemoveThenAddListeners(bool testHorizontal)
  109. {
  110. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  111. Scrollbar scrollbar = testHorizontal ? scrollRect.horizontalScrollbar : scrollRect.verticalScrollbar;
  112. GameObject scrollBarGO = new GameObject("scrollBar", typeof(RectTransform), typeof(Scrollbar));
  113. scrollBarGO.transform.SetParent(scrollRect.transform);
  114. Scrollbar newScrollbar = scrollBarGO.GetComponent<Scrollbar>();
  115. FieldInfo field = newScrollbar.onValueChanged.GetType().BaseType.BaseType.GetField("m_Calls", BindingFlags.NonPublic | BindingFlags.Instance);
  116. PropertyInfo property = field.GetValue(newScrollbar.onValueChanged).GetType().GetProperty("Count", BindingFlags.Public | BindingFlags.Instance);
  117. int newCallCount = (int)property.GetValue(field.GetValue(newScrollbar.onValueChanged), null);
  118. if (testHorizontal)
  119. scrollRect.horizontalScrollbar = newScrollbar;
  120. else
  121. scrollRect.verticalScrollbar = newScrollbar;
  122. Assert.AreEqual(0, (int)property.GetValue(field.GetValue(scrollbar.onValueChanged), null), "The previous scrollbar should not have listeners anymore");
  123. Assert.AreEqual(newCallCount + 1, (int)property.GetValue(field.GetValue(newScrollbar.onValueChanged), null), "The new scrollbar should have listeners now");
  124. }
  125. #endregion
  126. #region Drag
  127. [Test]
  128. [TestCase(PointerEventData.InputButton.Left, true)]
  129. [TestCase(PointerEventData.InputButton.Right, false)]
  130. [TestCase(PointerEventData.InputButton.Middle, false)]
  131. public void PotentialDragNeedsLeftClick(PointerEventData.InputButton button, bool expectedEqualsZero)
  132. {
  133. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  134. scrollRect.velocity = Vector2.one;
  135. Assert.AreNotEqual(Vector2.zero, scrollRect.velocity);
  136. scrollRect.OnInitializePotentialDrag(new PointerEventData(m_PrefabRoot.GetComponentInChildren<EventSystem>()) { button = button });
  137. if (expectedEqualsZero)
  138. Assert.AreEqual(Vector2.zero, scrollRect.velocity);
  139. else
  140. Assert.AreNotEqual(Vector2.zero, scrollRect.velocity);
  141. }
  142. [Test]
  143. [TestCase(PointerEventData.InputButton.Left, true, true)]
  144. [TestCase(PointerEventData.InputButton.Left, false, false)]
  145. [TestCase(PointerEventData.InputButton.Right, true, false)]
  146. [TestCase(PointerEventData.InputButton.Middle, true, false)]
  147. public void LeftClickShouldStartDrag(PointerEventData.InputButton button, bool active, bool expectedIsDragging)
  148. {
  149. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  150. scrollRect.enabled = active;
  151. scrollRect.velocity = Vector2.one;
  152. Assert.AreNotEqual(Vector2.zero, scrollRect.velocity);
  153. var pointerEventData = new PointerEventData(m_PrefabRoot.GetComponentInChildren<EventSystem>()) { button = button };
  154. scrollRect.OnInitializePotentialDrag(pointerEventData);
  155. FieldInfo field = typeof(ScrollRect).GetField("m_Dragging", BindingFlags.NonPublic | BindingFlags.Instance);
  156. Assert.IsFalse((bool)field.GetValue(scrollRect));
  157. scrollRect.OnBeginDrag(pointerEventData);
  158. Assert.AreEqual(expectedIsDragging, (bool)field.GetValue(scrollRect));
  159. }
  160. [Test]
  161. [TestCase(PointerEventData.InputButton.Left, true, false)]
  162. [TestCase(PointerEventData.InputButton.Left, false, false)]
  163. [TestCase(PointerEventData.InputButton.Right, false, false)]
  164. [TestCase(PointerEventData.InputButton.Right, true, true)]
  165. [TestCase(PointerEventData.InputButton.Middle, true, true)]
  166. [TestCase(PointerEventData.InputButton.Middle, false, false)]
  167. public void LeftClickUpShouldEndDrag(PointerEventData.InputButton button, bool active, bool expectedIsDragging)
  168. {
  169. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  170. scrollRect.velocity = Vector2.one;
  171. Assert.AreNotEqual(Vector2.zero, scrollRect.velocity);
  172. var startDragEventData = new PointerEventData(m_PrefabRoot.GetComponentInChildren<EventSystem>()) { button = PointerEventData.InputButton.Left };
  173. scrollRect.OnInitializePotentialDrag(startDragEventData);
  174. FieldInfo field = typeof(ScrollRect).GetField("m_Dragging", BindingFlags.NonPublic | BindingFlags.Instance);
  175. Assert.IsFalse((bool)field.GetValue(scrollRect));
  176. scrollRect.OnBeginDrag(startDragEventData);
  177. Assert.IsTrue((bool)field.GetValue(scrollRect), "Prerequisite: dragging should be true to test if it is set to false later");
  178. scrollRect.enabled = active;
  179. var endDragEventData = new PointerEventData(m_PrefabRoot.GetComponentInChildren<EventSystem>()) { button = button };
  180. scrollRect.OnEndDrag(endDragEventData);
  181. Assert.AreEqual(expectedIsDragging, (bool)field.GetValue(scrollRect));
  182. }
  183. #endregion
  184. #region LateUpdate
  185. [UnityTest]
  186. public IEnumerator LateUpdateWithoutInertiaOrElasticShouldZeroVelocity()
  187. {
  188. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  189. scrollRect.velocity = Vector2.one;
  190. scrollRect.inertia = false;
  191. scrollRect.movementType = ScrollRect.MovementType.Clamped;
  192. yield return null;
  193. Assert.AreEqual(Vector2.zero, scrollRect.velocity);
  194. }
  195. [UnityTest]
  196. public IEnumerator LateUpdateWithInertiaShouldDecelerate()
  197. {
  198. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  199. scrollRect.velocity = Vector2.one;
  200. scrollRect.inertia = true;
  201. scrollRect.movementType = ScrollRect.MovementType.Clamped;
  202. yield return null;
  203. Assert.Less(scrollRect.velocity.magnitude, 1);
  204. }
  205. [UnityTest][Ignore("Fails")]
  206. public IEnumerator LateUpdateWithElasticShouldDecelerate()
  207. {
  208. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  209. scrollRect.velocity = Vector2.one;
  210. scrollRect.inertia = false;
  211. scrollRect.content.anchoredPosition = Vector2.one * 2;
  212. scrollRect.movementType = ScrollRect.MovementType.Elastic;
  213. yield return null;
  214. Assert.AreNotEqual(1, scrollRect.velocity.magnitude);
  215. var newMagnitude = scrollRect.velocity.magnitude;
  216. yield return null;
  217. Assert.AreNotEqual(newMagnitude, scrollRect.velocity.magnitude);
  218. }
  219. [UnityTest]
  220. public IEnumerator LateUpdateWithElasticNoOffsetShouldZeroVelocity()
  221. {
  222. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  223. scrollRect.velocity = Vector2.one;
  224. scrollRect.inertia = false;
  225. scrollRect.movementType = ScrollRect.MovementType.Elastic;
  226. yield return null;
  227. Assert.AreEqual(Vector2.zero, scrollRect.velocity);
  228. }
  229. #endregion
  230. [Test]
  231. public void SetNormalizedPositionShouldSetContentLocalPosition()
  232. {
  233. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  234. scrollRect.normalizedPosition = Vector2.one;
  235. Assert.AreEqual(new Vector3(-1f, -1f, 0), scrollRect.content.localPosition);
  236. }
  237. #region Scroll, offset, ...
  238. [Test]
  239. [TestCase(1, 1, true, true, 1, -1, TestName = "Horizontal and vertical scroll")]
  240. [TestCase(1, 1, false, true, 0, -1, TestName = "Vertical scroll")]
  241. [TestCase(1, 1, true, false, 1, 0, TestName = "Horizontal scroll")]
  242. public void OnScrollClampedShouldMoveContentAnchoredPosition(int scrollDeltaX, int scrollDeltaY, bool horizontal,
  243. bool vertical, int expectedPosX, int expectedPosY)
  244. {
  245. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  246. Vector2 scrollDelta = new Vector2(scrollDeltaX, scrollDeltaY);
  247. var expected = new Vector2(expectedPosX, expectedPosY) * ScrollSensitivity;
  248. scrollRect.horizontal = horizontal;
  249. scrollRect.vertical = vertical;
  250. scrollRect.OnScroll(new PointerEventData(m_PrefabRoot.GetComponentInChildren<EventSystem>())
  251. {
  252. scrollDelta = scrollDelta
  253. });
  254. Assert.AreEqual(expected, scrollRect.content.anchoredPosition);
  255. }
  256. [Test][Ignore("Tests fail without mocking")]
  257. [TestCase(ScrollRect.MovementType.Clamped, 1f, 1f)]
  258. [TestCase(ScrollRect.MovementType.Unrestricted, 150, 150)]
  259. [TestCase(ScrollRect.MovementType.Elastic, 150, 150)]
  260. public void OnScrollClampedShouldClampContentAnchoredPosition(ScrollRect.MovementType movementType, float anchoredPosX,
  261. float anchoredPosY)
  262. {
  263. ScrollRect scrollRect = m_PrefabRoot.GetComponentInChildren<ScrollRect>();
  264. Vector2 scrollDelta = new Vector2(50, -50);
  265. scrollRect.movementType = movementType;
  266. scrollRect.content.anchoredPosition = new Vector2(2.5f, 2.5f);
  267. scrollRect.OnScroll(new PointerEventData(m_PrefabRoot.GetComponentInChildren<EventSystem>())
  268. {
  269. scrollDelta = scrollDelta
  270. });
  271. Assert.AreEqual(new Vector2(anchoredPosX, anchoredPosY), scrollRect.content.anchoredPosition);
  272. }
  273. [Test]
  274. public void GetBoundsShouldEncapsulateAllCorners()
  275. {
  276. Matrix4x4 matrix = Matrix4x4.identity;
  277. object[] arguments = new object[2]
  278. {
  279. new Vector3[] { Vector3.zero, Vector3.one, Vector3.one * 2, Vector3.one },
  280. matrix
  281. };
  282. MethodInfo method = typeof(ScrollRect).GetMethod("InternalGetBounds", BindingFlags.NonPublic | BindingFlags.Static);
  283. var bounds = (Bounds)method.Invoke(null, arguments);
  284. Assert.AreEqual(Vector3.zero, bounds.min);
  285. Assert.AreEqual(Vector3.one * 2, bounds.max);
  286. }
  287. [Test]
  288. public void UpdateBoundsShouldPad()
  289. {
  290. Bounds viewBounds = new Bounds(Vector3.zero, Vector3.one * 2);
  291. Vector3 contentSize = Vector3.one;
  292. Vector3 contentPos = Vector3.one;
  293. var contentPivot = new Vector2(0.5f, 0.5f);
  294. object[] arguments = new object[] { viewBounds, contentPivot, contentSize, contentPos };
  295. MethodInfo method = typeof(ScrollRect).GetMethod("AdjustBounds", BindingFlags.NonPublic | BindingFlags.Static);
  296. method.Invoke(null, arguments);
  297. //ScrollRect.AdjustBounds(ref viewBounds, ref contentPivot, ref contentSize, ref contentPos);
  298. Assert.AreEqual(new Vector3(2, 2, 1), arguments[2]);
  299. }
  300. [Test]
  301. [TestCase(true, true, 2, 4, -2, -2, TestName = "Should clamp offset")]
  302. [TestCase(false, true, 2, 4, 0, -2, TestName = "Vertical should clamp offset on one axis")]
  303. [TestCase(true, false, 2, 4, -2, 0, TestName = "Horizontal should clamp offset on one axis")]
  304. [TestCase(false, false, 2, 4, 0, 0, TestName = "No axis should not clamp offset")]
  305. [TestCase(true, true, 8, 10, 2, 2, TestName = "Should clamp negative offset")]
  306. [TestCase(false, true, 8, 10, 0, 2, TestName = "Vertical should clamp negative offset on one axis")]
  307. [TestCase(true, false, 8, 10, 2, 0, TestName = "Horizontal should clamp negative offset on one axis")]
  308. [TestCase(false, false, 8, 10, 0, 0, TestName = "No axis should not clamp negative offset")]
  309. public void CalculateOffsetShouldClamp(bool horizontal, bool vertical, int viewX, float viewY, float resX, float resY)
  310. {
  311. TestCalculateOffset(ScrollRect.MovementType.Clamped, horizontal, vertical, viewX, viewY, resX, resY, new Bounds(new Vector3(5, 7), new Vector3(4, 4)));
  312. }
  313. [Test]
  314. [TestCase(true, true, 2, 4, -2, -2, TestName = "Should clamp offset")]
  315. [TestCase(false, true, 2, 4, 0, -2, TestName = "Vertical should clamp offset on one axis")]
  316. [TestCase(true, false, 2, 4, -2, 0, TestName = "Horizontal should clamp offset on one axis")]
  317. [TestCase(false, false, 2, 4, 0, 0, TestName = "No axis should not clamp offset")]
  318. [TestCase(true, true, 8, 10, 2, 2, TestName = "Should clamp negative offset")]
  319. [TestCase(false, true, 8, 10, 0, 2, TestName = "Vertical should clamp negative offset on one axis")]
  320. [TestCase(true, false, 8, 10, 2, 0, TestName = "Horizontal should clamp negative offset on one axis")]
  321. [TestCase(false, false, 8, 10, 0, 0, TestName = "No axis should not clamp negative offset")]
  322. public void CalculateOffsetUnrestrictedShouldNotClamp(bool horizontal, bool vertical, int viewX, float viewY, float resX, float resY)
  323. {
  324. TestCalculateOffset(ScrollRect.MovementType.Unrestricted, horizontal, vertical, viewX, viewY, 0, 0, new Bounds(new Vector3(5, 7), new Vector3(4, 4)));
  325. }
  326. private static void TestCalculateOffset(ScrollRect.MovementType movementType, bool horizontal, bool vertical, int viewX, float viewY, float resX, float resY, Bounds contentBounds)
  327. {
  328. Bounds viewBounds = new Bounds(new Vector3(viewX, viewY), new Vector3(2, 2));
  329. // content is south east of view
  330. Vector2 delta = Vector2.zero;
  331. object[] arguments = new object[] { viewBounds, contentBounds, horizontal, vertical, movementType, delta };
  332. MethodInfo method = typeof(ScrollRect).GetMethod("InternalCalculateOffset", BindingFlags.NonPublic | BindingFlags.Static);
  333. var result = (Vector2)method.Invoke(null, arguments);
  334. Console.WriteLine(result);
  335. Assert.AreEqual(new Vector2(resX, resY), result);
  336. }
  337. #endregion
  338. }