Skip to content

Commit 5556836

Browse files
authored
NEW: Added tests for Input Action Editor UI for managing action maps (ISX-2087) (#1977)
* Added tests for Input Action Editor UI for managing action maps
1 parent c022f7f commit 5556836

File tree

8 files changed

+369
-15
lines changed

8 files changed

+369
-15
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// UITK TreeView is not supported in earlier versions
2+
// Therefore the UITK version of the InputActionAsset Editor is not available on earlier Editor versions either.
3+
#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS && UNITY_6000_0_OR_NEWER
4+
5+
using NUnit.Framework;
6+
using System;
7+
using System.Collections;
8+
using UnityEditor;
9+
using UnityEngine;
10+
using UnityEngine.InputSystem;
11+
using UnityEngine.InputSystem.Editor;
12+
using UnityEngine.TestTools;
13+
using UnityEngine.UIElements;
14+
15+
internal class InputActionsEditorTests : UIToolkitBaseTestWindow<InputActionsEditorWindow>
16+
{
17+
#region setup and teardown
18+
InputActionAsset m_Asset;
19+
20+
public override void OneTimeSetUp()
21+
{
22+
base.OneTimeSetUp();
23+
m_Asset = AssetDatabaseUtils.CreateAsset<InputActionAsset>();
24+
m_Asset.AddActionMap("First Name");
25+
m_Asset.AddActionMap("Second Name");
26+
m_Asset.AddActionMap("Third Name");
27+
}
28+
29+
public override void OneTimeTearDown()
30+
{
31+
AssetDatabaseUtils.Restore();
32+
base.OneTimeTearDown();
33+
}
34+
35+
public override IEnumerator UnitySetup()
36+
{
37+
m_Window = InputActionsEditorWindow.OpenEditor(m_Asset);
38+
yield return base.UnitySetup();
39+
}
40+
41+
#endregion
42+
43+
#region Helper methods
44+
45+
IEnumerator WaitForActionMapRename(int index, bool isActive, double timeoutSecs = 5.0)
46+
{
47+
return WaitUntil(() =>
48+
{
49+
var actionMapItems = m_Window.rootVisualElement.Q("action-maps-container").Query<InputActionMapsTreeViewItem>().ToList();
50+
if (actionMapItems.Count > index && actionMapItems[index].IsFocused == isActive)
51+
{
52+
return true;
53+
}
54+
return false;
55+
}, $"WaitForActionMapRename {index} {isActive}", timeoutSecs);
56+
}
57+
58+
#endregion
59+
60+
[Test]
61+
public void CanListActionMaps()
62+
{
63+
var actionMapsContainer = m_Window.rootVisualElement.Q("action-maps-container");
64+
Assert.That(actionMapsContainer, Is.Not.Null);
65+
var actionMapItem = actionMapsContainer.Query<InputActionMapsTreeViewItem>().ToList();
66+
Assert.That(actionMapItem, Is.Not.Null);
67+
Assert.That(actionMapItem.Count, Is.EqualTo(3));
68+
Assert.That(m_Window.currentAssetInEditor.actionMaps[0].name, Is.EqualTo("First Name"));
69+
Assert.That(m_Window.currentAssetInEditor.actionMaps[1].name, Is.EqualTo("Second Name"));
70+
Assert.That(m_Window.currentAssetInEditor.actionMaps[2].name, Is.EqualTo("Third Name"));
71+
}
72+
73+
[UnityTest]
74+
public IEnumerator CanCreateActionMap()
75+
{
76+
var button = m_Window.rootVisualElement.Q<Button>("add-new-action-map-button");
77+
Assume.That(button, Is.Not.Null);
78+
SimulateClickOn(button);
79+
80+
// Wait for the focus to move out the button and start new ActionMaps editon
81+
yield return WaitForActionMapRename(3, isActive: true);
82+
83+
// Rename the new action map
84+
SimulateTypingText("New Name");
85+
86+
// wait for the edition to end
87+
yield return WaitForActionMapRename(3, isActive: false);
88+
89+
// Check on the UI side
90+
var actionMapsContainer = m_Window.rootVisualElement.Q("action-maps-container");
91+
var actionMapItem = actionMapsContainer.Query<InputActionMapsTreeViewItem>().ToList();
92+
Assert.That(actionMapItem, Is.Not.Null);
93+
Assert.That(actionMapItem.Count, Is.EqualTo(4));
94+
Assert.That(actionMapItem[3].Q<Label>("name").text, Is.EqualTo("New Name"));
95+
96+
// Check on the asset side
97+
Assert.That(m_Window.currentAssetInEditor.actionMaps.Count, Is.EqualTo(4));
98+
Assert.That(m_Window.currentAssetInEditor.actionMaps[3].name, Is.EqualTo("New Name"));
99+
}
100+
101+
[UnityTest]
102+
public IEnumerator CanRenameActionMap()
103+
{
104+
var actionMapsContainer = m_Window.rootVisualElement.Q("action-maps-container");
105+
var actionMapItem = actionMapsContainer.Query<InputActionMapsTreeViewItem>().ToList();
106+
Assume.That(actionMapItem[1].Q<Label>("name").text, Is.EqualTo("Second Name"));
107+
108+
// for the selection the prevent some instabilities with current ui intregration
109+
m_Window.rootVisualElement.Q<ListView>("action-maps-list-view").Focus();
110+
m_Window.rootVisualElement.Q<ListView>("action-maps-list-view").selectedIndex = 1;
111+
112+
yield return WaitForNotDirty();
113+
yield return WaitForFocus(m_Window.rootVisualElement.Q("action-maps-list-view"));
114+
115+
// refetch the action map item since the ui may have refreshed.
116+
actionMapItem = actionMapsContainer.Query<InputActionMapsTreeViewItem>().ToList();
117+
118+
// clic twice to start the rename
119+
SimulateClickOn(actionMapItem[1]);
120+
// in case of the item is already focused, don't click again
121+
if (!actionMapItem[1].IsFocused)
122+
{
123+
SimulateClickOn(actionMapItem[1]);
124+
}
125+
126+
yield return WaitForActionMapRename(1, isActive: true);
127+
128+
// Rename the new action map
129+
SimulateTypingText("New Name");
130+
131+
// wait for the edition to end
132+
yield return WaitForActionMapRename(1, isActive: false);
133+
134+
// Check on the UI side
135+
actionMapsContainer = m_Window.rootVisualElement.Q("action-maps-container");
136+
Assume.That(actionMapsContainer, Is.Not.Null);
137+
actionMapItem = actionMapsContainer.Query<InputActionMapsTreeViewItem>().ToList();
138+
Assert.That(actionMapItem, Is.Not.Null);
139+
Assert.That(actionMapItem.Count, Is.EqualTo(3));
140+
Assert.That(actionMapItem[1].Q<Label>("name").text, Is.EqualTo("New Name"));
141+
142+
// Check on the asset side
143+
Assert.That(m_Window.currentAssetInEditor.actionMaps.Count, Is.EqualTo(3));
144+
Assert.That(m_Window.currentAssetInEditor.actionMaps[1].name, Is.EqualTo("New Name"));
145+
}
146+
147+
[UnityTest]
148+
public IEnumerator CanDeleteActionMap()
149+
{
150+
var actionMapsContainer = m_Window.rootVisualElement.Q("action-maps-container");
151+
var actionMapItem = actionMapsContainer.Query<InputActionMapsTreeViewItem>().ToList();
152+
Assume.That(actionMapItem[1].Q<Label>("name").text, Is.EqualTo("Second Name"));
153+
154+
// Select the second element
155+
SimulateClickOn(actionMapItem[1]);
156+
157+
yield return WaitForFocus(m_Window.rootVisualElement.Q("action-maps-list-view"));
158+
159+
SimulateDeleteCommand();
160+
161+
yield return WaitUntil(() => actionMapsContainer.Query<InputActionMapsTreeViewItem>().ToList().Count == 2, "wait for element to be deleted");
162+
163+
// Check on the UI side
164+
actionMapItem = actionMapsContainer.Query<InputActionMapsTreeViewItem>().ToList();
165+
Assert.That(actionMapItem.Count, Is.EqualTo(2));
166+
Assert.That(actionMapItem[0].Q<Label>("name").text, Is.EqualTo("First Name"));
167+
Assert.That(actionMapItem[1].Q<Label>("name").text, Is.EqualTo("Third Name"));
168+
169+
// Check on the asset side
170+
Assert.That(m_Window.currentAssetInEditor.actionMaps.Count, Is.EqualTo(2));
171+
Assert.That(m_Window.currentAssetInEditor.actionMaps[0].name, Is.EqualTo("First Name"));
172+
Assert.That(m_Window.currentAssetInEditor.actionMaps[1].name, Is.EqualTo("Third Name"));
173+
}
174+
}
175+
#endif

Assets/Tests/InputSystem.Editor/InputActionsEditorTests.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// UITK TreeView is not supported in earlier versions
2+
// Therefore the UITK version of the InputActionAsset Editor is not available on earlier Editor versions either.
3+
#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS && UNITY_6000_0_OR_NEWER
4+
5+
using NUnit.Framework;
6+
using System;
7+
using System.Collections;
8+
using UnityEditor;
9+
using UnityEngine;
10+
using UnityEngine.InputSystem;
11+
using UnityEngine.InputSystem.Editor;
12+
using UnityEngine.TestTools;
13+
using UnityEngine.UIElements;
14+
15+
public class UIToolkitBaseTestWindow<T> where T : EditorWindow
16+
{
17+
protected T m_Window;
18+
19+
#region setup and teardown
20+
[OneTimeSetUp]
21+
public virtual void OneTimeSetUp()
22+
{
23+
TestUtils.MockDialogs();
24+
}
25+
26+
[OneTimeTearDown]
27+
public virtual void OneTimeTearDown()
28+
{
29+
TestUtils.RestoreDialogs();
30+
}
31+
32+
[UnitySetUp]
33+
public virtual IEnumerator UnitySetup()
34+
{
35+
if (m_Window == null) yield break;
36+
yield return WaitForNotDirty();
37+
}
38+
39+
[TearDown]
40+
public virtual void TearDown()
41+
{
42+
m_Window?.Close();
43+
}
44+
45+
#endregion
46+
47+
#region Helper methods
48+
49+
/// <summary>
50+
/// Simulate a click at the center of the target element
51+
/// </summary>
52+
/// <param name="target"></param>
53+
protected void SimulateClickOn(VisualElement target)
54+
{
55+
Event evtd = new Event();
56+
evtd.type = EventType.MouseDown;
57+
evtd.mousePosition = target.worldBound.center;
58+
evtd.clickCount = 1;
59+
using var pde = PointerDownEvent.GetPooled(evtd);
60+
target.SendEvent(pde);
61+
62+
Event evtu = new Event();
63+
evtu.type = EventType.MouseUp;
64+
evtu.mousePosition = target.worldBound.center;
65+
evtu.clickCount = 1;
66+
using var pue = PointerUpEvent.GetPooled(evtu);
67+
target.SendEvent(pue);
68+
}
69+
70+
/// <summary>
71+
/// Simulate typing text into the window
72+
/// It will be dispatched by the panel the currently focused element
73+
/// </summary>
74+
/// <param name="text">The text to sned</param>
75+
/// <param name="sendReturnKeyAfterTyping">Send a Return key after typing the last character</param>
76+
protected void SimulateTypingText(string text, bool sendReturnKeyAfterTyping = true)
77+
{
78+
foreach (var character in text)
79+
{
80+
var evtd = new Event() { type = EventType.KeyDown, keyCode = KeyCode.None, character = character };
81+
using var kde = KeyDownEvent.GetPooled(evtd);
82+
m_Window.rootVisualElement.SendEvent(kde);
83+
84+
var evtu = new Event() { type = EventType.KeyUp, keyCode = KeyCode.None, character = character };
85+
using var kue = KeyUpEvent.GetPooled(evtu);
86+
m_Window.rootVisualElement.SendEvent(kue);
87+
}
88+
if (sendReturnKeyAfterTyping)
89+
{
90+
SimulateReturnKey();
91+
}
92+
}
93+
94+
/// <summary>
95+
/// Simulate a Return key press
96+
/// It will be dispatched by the panel the currently focused element
97+
/// </summary>
98+
protected void SimulateReturnKey()
99+
{
100+
var evtd = new Event() { type = EventType.KeyDown, keyCode = KeyCode.Return };
101+
using var kde = KeyDownEvent.GetPooled(evtd);
102+
var evtd2 = new Event() { type = EventType.KeyDown, keyCode = KeyCode.None, character = '\n' };
103+
using var kde2 = KeyDownEvent.GetPooled(evtd2);
104+
m_Window.rootVisualElement.SendEvent(kde);
105+
m_Window.rootVisualElement.SendEvent(kde2);
106+
107+
var evtu = new Event() { type = EventType.KeyUp, keyCode = KeyCode.Return };
108+
using var kue = KeyUpEvent.GetPooled(evtu);
109+
m_Window.rootVisualElement.SendEvent(kue);
110+
}
111+
112+
/// <summary>
113+
/// Simulate sending a Delete Command
114+
/// It will be dispatched by the panel the currently focused element
115+
/// </summary>
116+
protected void SimulateDeleteCommand()
117+
{
118+
var evt = new Event() { type = EventType.ExecuteCommand, commandName = "Delete" };
119+
using var ce = ExecuteCommandEvent.GetPooled(evt);
120+
m_Window.rootVisualElement.SendEvent(ce);
121+
}
122+
123+
/// <summary>
124+
/// Wait for the visual element to be focused
125+
/// </summary>
126+
/// <param name="ve">VisualElement to be focused</param>
127+
/// <param name="timeoutSecs">Maximum time to wait in seconds.</param>
128+
protected IEnumerator WaitForFocus(VisualElement ve, double timeoutSecs = 5.0)
129+
{
130+
return WaitUntil(() => ve.focusController.focusedElement == ve, "WaitForFocus", timeoutSecs);
131+
}
132+
133+
/// <summary>
134+
/// Wait for the windows to be not dirty
135+
/// </summary>
136+
/// <param name="timeoutSecs">Maximum time to wait in seconds.</param>
137+
protected IEnumerator WaitForNotDirty(double timeoutSecs = 5.0)
138+
{
139+
return WaitUntil(() => m_Window.rootVisualElement.panel.isDirty == false, "WaitForNotDirty", timeoutSecs);
140+
}
141+
142+
/// <summary>
143+
/// Wait until the action is true or the timeout is reached with an assertion
144+
/// </summary>
145+
/// <param name="action">Lambda to call between frame</param>
146+
/// <param name="assertMessage">Assert Message</param>
147+
/// <param name="timeoutSecs">Maximum time to wait in seconds.</param>
148+
protected IEnumerator WaitUntil(Func<bool> action, string assertMessage, double timeoutSecs = 5.0)
149+
{
150+
var endTime = EditorApplication.timeSinceStartup + timeoutSecs;
151+
do
152+
{
153+
if (action()) yield break;
154+
yield return null;
155+
}
156+
while (endTime > EditorApplication.timeSinceStartup);
157+
Assert.That(action(), assertMessage);
158+
}
159+
160+
#endregion
161+
}
162+
#endif

Assets/Tests/InputSystem.Editor/UIToolkitBaseTestWindow.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ however, it has to be formatted properly to pass verification tests.
1212

1313
### Fixed
1414
- Fixed memory allocation on every frame when using UIDocument without EventSystem. [ISXB-953](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-953)
15+
- Fixed Action Maps name edition which could be inconsistent in Input Action Editor UI.
1516

1617
### Changed
1718
- Use `ProfilerMarker` instead of `Profiler.BeginSample` and `Profiler.EndSample` when appropriate to enable recording of profiling data.
1819

20+
### Added
21+
- Added tests for Input Action Editor UI for managing action maps (List, create, rename, delete) (ISX-2087)
22+
1923
## [1.10.0] - 2024-07-24
2024

2125
### Fixed

Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorWindow.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ static InputActionsEditorWindow()
3030
}
3131

3232
static readonly Vector2 k_MinWindowSize = new Vector2(650, 450);
33-
33+
// For UI testing purpose
34+
internal InputActionAsset currentAssetInEditor => m_AssetObjectForEditing;
3435
[SerializeField] private InputActionAsset m_AssetObjectForEditing;
3536
[SerializeField] private InputActionsEditorState m_State;
3637
[SerializeField] private string m_AssetGUID;

Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionMapsView.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public override void RedrawUI(ViewState viewState)
9090
if (actionMapData.HasValue)
9191
m_ListView.SetSelection(viewState.actionMapData.IndexOf(actionMapData.Value));
9292
}
93-
m_ListView.Rebuild();
93+
m_ListView.RefreshItems();
9494
RenameNewActionMaps();
9595
}
9696

0 commit comments

Comments
 (0)