Files
2026-11-01 23:39:55 +02:00

179 lines
7.3 KiB
C#

using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.UI;
namespace LayoutTests
{
[UnityPlatform()]
internal class LayoutGroupTests
{
Canvas m_Canvas;
[SetUp]
public void TestSetup()
{
m_Canvas = new GameObject("Canvas").AddComponent<Canvas>();
m_Canvas.renderMode = RenderMode.ScreenSpaceOverlay;
}
[TearDown]
public void TearDown()
{
GameObject.DestroyImmediate(m_Canvas.gameObject);
}
[UnityTest]
public IEnumerator EmptyRecttransformUpdatesLayoutGroup()
{
// Canvas
// ....LayoutGroup
// ........"pure" RectTransform <--- we modify the dimensions of this
// ........Image
// Create VerticalLayoutGroup with spacing=10
var layoutGroup = new GameObject("LayoutGroup").AddComponent<RectTransform>();
layoutGroup.pivot = new Vector2(0, 1);
layoutGroup.SetParent(m_Canvas.transform, false);
var verticalLayoutGroup = layoutGroup.gameObject.AddComponent<VerticalLayoutGroup>();
verticalLayoutGroup.childForceExpandWidth = false;
verticalLayoutGroup.childForceExpandHeight = false;
verticalLayoutGroup.childControlWidth = false;
verticalLayoutGroup.childControlHeight = false;
verticalLayoutGroup.spacing = 10;
// Add an empty RectTransfrom with height=100 to the layout group
var emptyChild = new GameObject("EmptyRectTransform").AddComponent<RectTransform>();
emptyChild.pivot = new Vector2(0, 1);
emptyChild.SetParent(layoutGroup, false);
emptyChild.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 100f);
// Add an image as a sibbling after the empty RectTransform
var image = new GameObject("Image").AddComponent<RectTransform>();
image.pivot = new Vector2(0, 1);
image.transform.SetParent(layoutGroup, false);
image.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 100f);
image.gameObject.AddComponent<Image>();
yield return null;
// Verify the test setup
Assert.AreEqual(0f, emptyChild.anchoredPosition.y);
Assert.AreEqual(-110f, image.anchoredPosition.y); // -10 for spacing
// Expand the empty child
emptyChild.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 200f);
yield return null;
// Expanding the empty child should have triggered the layout group to rebuild and push the image down
Assert.AreEqual(0f, emptyChild.anchoredPosition.y);
Assert.AreEqual(-210f, image.anchoredPosition.y);
}
// LayoutGroup.Reset turns childControl* off when the component is added, so the sizing
// flags are set explicitly rather than inherited from the serialized defaults.
static T CreateGroup<T>(Transform parent, RectOffset padding) where T : HorizontalOrVerticalLayoutGroup
{
var rectTransform = new GameObject(typeof(T).Name).AddComponent<RectTransform>();
rectTransform.SetParent(parent, false);
var group = rectTransform.gameObject.AddComponent<T>();
group.padding = padding;
group.childControlWidth = true;
group.childControlHeight = true;
group.childForceExpandWidth = false;
group.childForceExpandHeight = false;
return group;
}
static LayoutElement CreateElement(Transform parent, float preferredWidth)
{
var rectTransform = new GameObject("Element").AddComponent<RectTransform>();
rectTransform.SetParent(parent, false);
var element = rectTransform.gameObject.AddComponent<LayoutElement>();
element.preferredWidth = preferredWidth;
return element;
}
// A group with no children has no content, so it imposes no upper bound on its own
// size: the maximum is DefaultMaxSize, not the padding.
[Test]
public void EmptyHorizontalLayoutGroupDoesNotConstrainMaxWidth()
{
var group = CreateGroup<HorizontalLayoutGroup>(m_Canvas.transform, new RectOffset(2, 4, 3, 5));
group.CalculateLayoutInputHorizontal();
Assert.AreEqual(LayoutUtility.DefaultMaxSize, group.maxWidth);
}
[Test]
public void EmptyVerticalLayoutGroupDoesNotConstrainMaxHeight()
{
var group = CreateGroup<VerticalLayoutGroup>(m_Canvas.transform, new RectOffset(2, 4, 3, 5));
group.CalculateLayoutInputVertical();
Assert.AreEqual(LayoutUtility.DefaultMaxSize, group.maxHeight);
}
// rectChildren excludes inactive children, so a group whose children are all disabled
// is an empty group.
[Test]
public void HorizontalLayoutGroupWithOnlyInactiveChildrenDoesNotConstrainMaxWidth()
{
var group = CreateGroup<HorizontalLayoutGroup>(m_Canvas.transform, new RectOffset(2, 4, 3, 5));
CreateElement(group.transform, 100).gameObject.SetActive(false);
CreateElement(group.transform, 200).gameObject.SetActive(false);
group.CalculateLayoutInputHorizontal();
Assert.AreEqual(LayoutUtility.DefaultMaxSize, group.maxWidth);
}
// LayoutElement (priority 1) supplies the preferred width and leaves maxWidth unset, so
// the maximum resolves from the LayoutGroup (priority 0) on the same GameObject. An empty
// group must not cap that preferred width.
[Test]
public void EmptyLayoutGroupDoesNotClampLayoutElementPreferredWidthOnSameGameObject()
{
var group = CreateGroup<HorizontalLayoutGroup>(m_Canvas.transform, new RectOffset());
var element = group.gameObject.AddComponent<LayoutElement>();
element.preferredWidth = 180;
group.CalculateLayoutInputHorizontal();
Assert.AreEqual(180, LayoutUtility.GetPreferredWidth(group.transform as RectTransform));
}
// On a group's non-primary axis the maximum is a Mathf.Min across children, so an empty
// descendant must not cap the parent below what its siblings ask for.
[Test]
public void EmptyChildGroupDoesNotCollapseParentPreferredWidth()
{
var parent = CreateGroup<VerticalLayoutGroup>(m_Canvas.transform, new RectOffset());
CreateElement(parent.transform, 180);
CreateGroup<HorizontalLayoutGroup>(parent.transform, new RectOffset());
LayoutRebuilder.ForceRebuildLayoutImmediate(parent.transform as RectTransform);
Assert.AreEqual(180, parent.preferredWidth);
}
// A child that genuinely declares a maximum still caps the group.
[Test]
public void NonEmptyLayoutGroupStillHonoursChildMaxWidth()
{
var group = CreateGroup<HorizontalLayoutGroup>(m_Canvas.transform, new RectOffset());
var element = CreateElement(group.transform, 200);
element.maxWidth = 50;
group.CalculateLayoutInputHorizontal();
Assert.AreEqual(50, group.maxWidth);
Assert.AreEqual(50, group.preferredWidth);
}
}
}