223 lines
8.7 KiB
C#
223 lines
8.7 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using NUnit.Framework;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using XGame.VirtualInput;
|
|
|
|
public sealed class VirtualActionButtonTests
|
|
{
|
|
private readonly List<GameObject> _roots = new List<GameObject>();
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
foreach (GameObject root in _roots)
|
|
{
|
|
if (root != null)
|
|
{
|
|
Object.DestroyImmediate(root);
|
|
}
|
|
}
|
|
_roots.Clear();
|
|
}
|
|
|
|
[Test]
|
|
public void PointerTap_EmitsDownThenTap()
|
|
{
|
|
VirtualActionButton button = CreateButton(ActionButtonId.Primary);
|
|
List<ActionButtonEvent> events = Capture(button);
|
|
|
|
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, Time.unscaledTime);
|
|
button.HandlePointerUpForTest(new Vector2(102f, 100f), 1, 0.1f);
|
|
|
|
Assert.That(events.Select(e => e.Phase), Is.EqualTo(new[] { ActionButtonPhase.Down, ActionButtonPhase.Tap }));
|
|
Assert.That(events.Last().ButtonId, Is.EqualTo(ActionButtonId.Primary));
|
|
}
|
|
|
|
[Test]
|
|
public void PointerUpInsideButtonWithoutMeaningfulDrag_EmitsTapEvenNearButtonEdge()
|
|
{
|
|
VirtualActionButton button = CreateButton(ActionButtonId.Skill1);
|
|
List<ActionButtonEvent> events = Capture(button);
|
|
|
|
button.HandlePointerDownForTest(new Vector2(140f, 100f), 1, 0f);
|
|
button.HandlePointerUpForTest(new Vector2(141f, 100f), 1, 0.1f);
|
|
|
|
Assert.That(events.Select(e => e.Phase), Is.EqualTo(new[] { ActionButtonPhase.Down, ActionButtonPhase.Tap }));
|
|
}
|
|
|
|
[Test]
|
|
public void PointerDragPastThreshold_EmitsReleaseWithDirection()
|
|
{
|
|
VirtualActionButton button = CreateButton(ActionButtonId.Skill1);
|
|
List<ActionButtonEvent> events = Capture(button);
|
|
|
|
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
|
button.HandleDragForTest(new Vector2(160f, 100f), 1, 0.1f);
|
|
button.HandlePointerUpForTest(new Vector2(160f, 100f), 1, 0.2f);
|
|
|
|
ActionButtonEvent release = events.Last();
|
|
Assert.That(release.Phase, Is.EqualTo(ActionButtonPhase.Release));
|
|
Assert.That(release.Direction.x, Is.EqualTo(1f).Within(0.001f));
|
|
Assert.That(release.Direction.y, Is.EqualTo(0f).Within(0.001f));
|
|
Assert.That(release.Distance01, Is.GreaterThan(0.7f));
|
|
}
|
|
|
|
[Test]
|
|
public void PointerUpInsideCancelArea_EmitsCancel()
|
|
{
|
|
VirtualActionButton button = CreateButton(ActionButtonId.Skill2);
|
|
RectTransform cancelArea = CreateRect("CancelArea", new Vector2(200f, 100f), new Vector2(80f, 80f));
|
|
button.SetCancelArea(cancelArea);
|
|
List<ActionButtonEvent> events = Capture(button);
|
|
|
|
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
|
button.HandleDragForTest(new Vector2(200f, 100f), 1, 0.1f);
|
|
button.HandlePointerUpForTest(new Vector2(200f, 100f), 1, 0.2f);
|
|
|
|
Assert.That(events.Last().Phase, Is.EqualTo(ActionButtonPhase.Cancel));
|
|
Assert.That(events.Last().IsCanceled, Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void Cooldown_EmitsBlockedOnly()
|
|
{
|
|
VirtualActionButton button = CreateButton(ActionButtonId.Skill3);
|
|
List<ActionButtonEvent> events = Capture(button);
|
|
|
|
button.SetCooldown(3f, 5f);
|
|
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
|
|
|
Assert.That(events.Select(e => e.Phase), Is.EqualTo(new[] { ActionButtonPhase.Blocked }));
|
|
}
|
|
|
|
[Test]
|
|
public void ChargesZero_EmitsBlockedAndShowsChargeText()
|
|
{
|
|
VirtualActionButton button = CreateButton(ActionButtonId.Skill4, out _, out TMP_Text chargeText);
|
|
List<ActionButtonEvent> events = Capture(button);
|
|
|
|
button.SetCharges(0);
|
|
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
|
|
|
Assert.That(events.Single().Phase, Is.EqualTo(ActionButtonPhase.Blocked));
|
|
Assert.That(chargeText.text, Is.EqualTo("0"));
|
|
}
|
|
|
|
[Test]
|
|
public void BindNodes_FindsPrefabChildrenWithButtonPrefix()
|
|
{
|
|
VirtualActionButton button = CreateButton(ActionButtonId.Skill1, "Skill1", out _, out TMP_Text chargeText);
|
|
|
|
button.SetCharges(2);
|
|
|
|
Assert.That(chargeText.text, Is.EqualTo("2"));
|
|
Assert.That(chargeText.gameObject.activeSelf, Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void Hold_EmitsHoldStartTickAndEnd()
|
|
{
|
|
VirtualActionButton button = CreateButton(ActionButtonId.Primary);
|
|
List<ActionButtonEvent> events = Capture(button);
|
|
|
|
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
|
button.HandleDragForTest(new Vector2(102f, 100f), 1, 0.4f);
|
|
button.HandleDragForTest(new Vector2(102f, 100f), 1, 0.51f);
|
|
button.HandlePointerUpForTest(new Vector2(102f, 100f), 1, 0.6f);
|
|
|
|
Assert.That(events.Select(e => e.Phase), Does.Contain(ActionButtonPhase.HoldStart));
|
|
Assert.That(events.Select(e => e.Phase), Does.Contain(ActionButtonPhase.HoldTick));
|
|
Assert.That(events.Select(e => e.Phase), Does.Contain(ActionButtonPhase.HoldEnd));
|
|
}
|
|
|
|
[Test]
|
|
public void Update_EmitsHoldWhilePointerIsStationary()
|
|
{
|
|
VirtualActionButton button = CreateButton(ActionButtonId.Primary);
|
|
List<ActionButtonEvent> events = Capture(button);
|
|
float startedAt = Time.unscaledTime - 0.4f;
|
|
|
|
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, startedAt);
|
|
InvokePrivate(button, "Update");
|
|
|
|
Assert.That(events.Select(e => e.Phase), Does.Contain(ActionButtonPhase.HoldStart));
|
|
}
|
|
|
|
[Test]
|
|
public void OnDisable_CancelsActivePointer()
|
|
{
|
|
VirtualActionButton button = CreateButton(ActionButtonId.Skill2);
|
|
List<ActionButtonEvent> events = Capture(button);
|
|
|
|
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
|
InvokePrivate(button, "OnDisable");
|
|
|
|
Assert.That(events.Select(e => e.Phase), Is.EqualTo(new[] { ActionButtonPhase.Down, ActionButtonPhase.Cancel }));
|
|
}
|
|
|
|
private VirtualActionButton CreateButton(ActionButtonId id)
|
|
{
|
|
return CreateButton(id, out _, out _);
|
|
}
|
|
|
|
private VirtualActionButton CreateButton(ActionButtonId id, out Image cooldownMask, out TMP_Text chargeText)
|
|
{
|
|
return CreateButton(id, null, out cooldownMask, out chargeText);
|
|
}
|
|
|
|
private VirtualActionButton CreateButton(ActionButtonId id, string childPrefix, out Image cooldownMask, out TMP_Text chargeText)
|
|
{
|
|
RectTransform rect = CreateRect("Btn_" + id, new Vector2(100f, 100f), new Vector2(100f, 100f));
|
|
string prefix = string.IsNullOrEmpty(childPrefix) ? string.Empty : childPrefix + "_";
|
|
new GameObject(prefix + "Icon", typeof(RectTransform), typeof(Image)).transform.SetParent(rect, false);
|
|
cooldownMask = new GameObject(prefix + "CooldownMask", typeof(RectTransform), typeof(Image)).GetComponent<Image>();
|
|
cooldownMask.transform.SetParent(rect, false);
|
|
chargeText = new GameObject(prefix + "ChargeText", typeof(RectTransform), typeof(TextMeshProUGUI)).GetComponent<TMP_Text>();
|
|
chargeText.transform.SetParent(rect, false);
|
|
new GameObject(prefix + "CooldownText", typeof(RectTransform), typeof(TextMeshProUGUI)).transform.SetParent(rect, false);
|
|
new GameObject(prefix + "AimIndicator", typeof(RectTransform)).transform.SetParent(rect, false);
|
|
|
|
VirtualActionButton button = rect.gameObject.AddComponent<VirtualActionButton>();
|
|
button.ButtonId = id;
|
|
SetPrivateField(button, "screenCenterOverride", new Vector2(100f, 100f));
|
|
SetPrivateField(button, "screenRadiusOverride", 50f);
|
|
InvokePrivate(button, "Awake");
|
|
return button;
|
|
}
|
|
|
|
private RectTransform CreateRect(string name, Vector2 position, Vector2 size)
|
|
{
|
|
GameObject go = new GameObject(name, typeof(RectTransform));
|
|
_roots.Add(go);
|
|
RectTransform rect = go.GetComponent<RectTransform>();
|
|
rect.position = position;
|
|
rect.sizeDelta = size;
|
|
return rect;
|
|
}
|
|
|
|
private static List<ActionButtonEvent> Capture(VirtualActionButton button)
|
|
{
|
|
var events = new List<ActionButtonEvent>();
|
|
button.ButtonEvent += events.Add;
|
|
return events;
|
|
}
|
|
|
|
private static void SetPrivateField(object target, string name, object value)
|
|
{
|
|
FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.That(field, Is.Not.Null);
|
|
field.SetValue(target, value);
|
|
}
|
|
|
|
private static void InvokePrivate(object target, string name, params object[] args)
|
|
{
|
|
MethodInfo method = target.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.That(method, Is.Not.Null);
|
|
method.Invoke(target, args);
|
|
}
|
|
}
|