Skip to content

Commit b02a52e

Browse files
committed
More bug fixes and test cases for input action reference
1 parent 7c5305a commit b02a52e

File tree

2 files changed

+134
-70
lines changed

2 files changed

+134
-70
lines changed

Assets/Tests/InputSystem/CoreTests_Actions_Reference.cs

Lines changed: 108 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
partial class CoreTests
88
{
9+
#region Helpers
10+
911
private static InputActionAsset CreateAssetWithTwoActions()
1012
{
1113
var map1 = new InputActionMap("map1");
@@ -25,68 +27,130 @@ private static InputActionAsset CreateAssetWithSingleAction()
2527
return asset;
2628
}
2729

28-
[Test]
29-
public void Actions_Reference_AssetAndActionsReturnsNull_IfNotSet()
30+
private void AssertDefaults(InputActionReference reference)
3031
{
31-
var reference = ScriptableObject.CreateInstance<InputActionReference>();
3232
Assert.That(reference.asset, Is.Null);
3333
Assert.That(reference.action, Is.Null);
34+
Assert.That(reference.name, Is.EqualTo(string.Empty));
3435
Assert.That(reference.ToDisplayName(), Is.Null);
36+
Assert.That(reference.ToString(), Is.EqualTo($" ({typeof(InputActionReference).FullName})"));
3537
}
3638

39+
#endregion
40+
3741
[Test]
38-
public void Actions_Reference_SetNull()
42+
[Category("Actions")]
43+
public void Actions_Reference_Defaults()
3944
{
45+
AssertDefaults(ScriptableObject.CreateInstance<InputActionReference>());
46+
}
47+
48+
[TestCase(false, "map1", "action1")]
49+
[TestCase(true, null, "action1")]
50+
[TestCase(true, "map1", null)]
51+
[Category("Actions")]
52+
public void Actions_Reference_SetByNameThrows_IfAnyArgumentIsNull(bool validAsset, string mapName, string actionName)
53+
{
54+
var asset = CreateAssetWithTwoActions();
4055
var reference = ScriptableObject.CreateInstance<InputActionReference>();
41-
reference.Set(null);
56+
Assert.Throws<ArgumentNullException>(() => reference.Set(validAsset ? asset : null, mapName, actionName));
4257
}
4358

44-
[Test]
59+
[TestCase("doesNotExist", "action1")]
60+
[TestCase("map1", "doesNotExist")]
4561
[Category("Actions")]
46-
public void Actions_Reference_CanResolveAction()
62+
public void Actions_Reference_SetByNameThrows_IfNoMatchingMapOrActionExists(string mapName, string actionName)
4763
{
4864
var asset = CreateAssetWithTwoActions();
4965
var reference = ScriptableObject.CreateInstance<InputActionReference>();
66+
Assert.Throws<ArgumentException>(() => reference.Set(asset, mapName, actionName));
67+
}
5068

51-
reference.Set(asset, "map1", "action2");
69+
[Test]
70+
[Category("Actions")]
71+
public void Actions_Reference_SetByReferenceThrows_IfActionDoNotBelongToAnAsset()
72+
{
73+
// Case 1: Action was never part of any asset
74+
var action = new InputAction("action");
75+
var reference = ScriptableObject.CreateInstance<InputActionReference>();
76+
Assert.Throws<InvalidOperationException>(() => reference.Set(action));
5277

53-
Assert.That(reference.action, Is.SameAs(asset.FindAction("map1/action2")));
78+
// Case 2: Action was part of an asset but then removed
79+
var asset = CreateAssetWithTwoActions();
80+
var action1 = asset.FindAction("map1/action1");
81+
asset.RemoveAction("map1/action1");
82+
Assert.Throws<InvalidOperationException>(() => reference.Set(action1));
5483
}
5584

5685
[Test]
5786
[Category("Actions")]
58-
public void Actions_Reference_CanResolveAction_EvenAfterActionHasBeenRenamed()
87+
public void Actions_Reference_CreateReturnsReference_WhenCreatedFromValidAction()
5988
{
60-
var map = new InputActionMap("map");
61-
var action = map.AddAction("oldName");
62-
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
63-
asset.AddActionMap(map);
89+
var asset = CreateAssetWithTwoActions();
90+
var action = asset.FindAction("map1/action2");
91+
92+
var reference = InputActionReference.Create(action);
93+
Assert.That(reference.action, Is.SameAs(action));
94+
}
95+
96+
[Test]
97+
[Category("Actions")]
98+
public void Actions_Reference_CreateReturnsNullReferenceObject_IfActionIsNull()
99+
{
100+
var reference = InputActionReference.Create(null);
101+
Assert.That(reference.action, Is.Null);
102+
}
103+
104+
[TestCase(false)]
105+
[TestCase(true)]
106+
[Category("Actions")]
107+
public void Actions_Reference_CanResolveAction_WhenSet(bool setByReference)
108+
{
109+
var asset = CreateAssetWithTwoActions();
110+
var action = asset.FindAction("map1/action2");
64111

65112
var reference = ScriptableObject.CreateInstance<InputActionReference>();
66-
reference.Set(asset, "map", "oldName");
113+
if (setByReference)
114+
reference.Set(action);
115+
else
116+
reference.Set(asset, "map1", "action2");
67117

68-
action.Rename("newName");
118+
Assert.That(reference.action, Is.SameAs(action));
119+
}
120+
121+
[Test]
122+
[Category("Actions")]
123+
public void Actions_Reference_CanResolveAction_AfterActionHasBeenRenamed()
124+
{
125+
var asset = CreateAssetWithSingleAction();
126+
var reference = ScriptableObject.CreateInstance<InputActionReference>();
127+
reference.Set(asset, "map2", "action3");
128+
var action = asset.FindAction("map2/action3");
69129

70-
var referencedAction = reference.action;
130+
action.Rename("newName");
131+
Assert.That(reference.action, Is.SameAs(action));
132+
}
71133

72-
Assert.That(referencedAction, Is.SameAs(action));
134+
[Test]
135+
[Category("Actions")]
136+
public void Actions_Reference_CanResolveToDefaultState_AfterActionHasBeenSetToNull()
137+
{
138+
var asset = CreateAssetWithTwoActions();
139+
var reference = InputActionReference.Create(asset.FindAction("map1/action1"));
140+
reference.Set(null);
141+
AssertDefaults(reference);
73142
}
74143

75144
[Test(Description = "https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1584")]
76145
[Category("Actions")]
77-
public void Actions_Reference_CanResolveActionAfterReassignment()
146+
public void Actions_Reference_CanResolveActionAfterReassignmentToActionFromAnotherAsset()
78147
{
79148
var asset1 = CreateAssetWithTwoActions();
80149
var asset2 = CreateAssetWithSingleAction();
81150

82151
var reference = ScriptableObject.CreateInstance<InputActionReference>();
83152
reference.Set(asset1, "map1", "action1");
84-
Assert.That(reference.action, Is.Not.Null);
85-
Assert.That(reference.action, Is.SameAs(asset1.FindAction("map1/action1"))); // Redundant, but important for test case
86-
Assert.That(reference.asset, Is.SameAs(asset1));
87-
Assert.That(reference.name, Is.EqualTo("map1/action1"));
88-
Assert.That(reference.ToDisplayName(), Is.EqualTo("map1/action1"));
89-
Assert.That(reference.ToString(), Is.EqualTo(":map1/action1"));
153+
Assert.That(reference.action, Is.Not.Null); // Looks redundant, but important for test case to resolve
90154

91155
reference.Set(asset2, "map2", "action3");
92156
Assert.That(reference.action, Is.Not.Null);
@@ -100,35 +164,38 @@ public void Actions_Reference_CanResolveActionAfterReassignment()
100164
[TestCase(typeof(InputAction))]
101165
[TestCase(typeof(InputActionMap))]
102166
[TestCase(typeof(InputActionAsset))]
103-
public void Actions_Reference_NameShouldReflectReferencedAction(Type typeToDelete)
167+
public void Actions_Reference_ShouldReevaluateIfAssociatedEntityIsDeleted(Type typeToDelete)
104168
{
105-
var map = new InputActionMap("map");
106-
var action1 = map.AddAction("action1");
107-
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
108-
asset.AddActionMap(map);
169+
var asset = CreateAssetWithTwoActions();
170+
var action = asset.FindAction("map1/action1");
171+
172+
// var map = new InputActionMap("map");
173+
// var action1 = map.AddAction("action1");
174+
// var asset = ScriptableObject.CreateInstance<InputActionAsset>();
175+
// asset.AddActionMap(map);
109176

110177
var reference = ScriptableObject.CreateInstance<InputActionReference>();
111-
reference.Set(asset, "map", "action1");
178+
reference.Set(asset, "map1", "action1");
112179

113-
Assert.That(reference.action, Is.SameAs(action1));
180+
Assert.That(reference.action, Is.SameAs(action));
114181
Assert.That(reference.asset, Is.SameAs(asset));
115-
Assert.That(reference.name, Is.EqualTo("map/action1"));
116-
Assert.That(reference.ToDisplayName(), Is.EqualTo("map/action1"));
117-
Assert.That(reference.ToString(), Is.EqualTo(":map/action1"));
182+
Assert.That(reference.name, Is.EqualTo("map1/action1"));
183+
Assert.That(reference.ToDisplayName(), Is.EqualTo("map1/action1"));
184+
Assert.That(reference.ToString(), Is.EqualTo(":map1/action1"));
118185

119186
// Delete the referenced action directly or indirectly
120187
if (typeToDelete == typeof(InputAction))
121-
asset.RemoveAction("map/action1");
188+
asset.RemoveAction("map1/action1");
122189
else if (typeToDelete == typeof(InputActionMap))
123-
asset.RemoveActionMap("map");
190+
asset.RemoveActionMap("map1");
124191
else if (typeToDelete == typeof(InputActionAsset))
125192
UnityEngine.Object.DestroyImmediate(asset);
126193

127194
// TODO reference need to react to this
128195
Assert.That(reference.action, Is.Null);
129196
Assert.That(reference.asset, Is.SameAs(asset));
130-
Assert.That(reference.name, Is.EqualTo("map/action1")); // Unexpected when no longer existing
131-
Assert.That(reference.ToDisplayName(), Is.EqualTo("map/action1")); // Unexpected when no longer existing
197+
Assert.That(reference.name, Is.EqualTo("map1/action1")); // Unexpected when no longer existing
198+
Assert.That(reference.ToDisplayName(), Is.EqualTo("map1/action1")); // Unexpected when no longer existing
132199
//Assert.That(reference.ToString(), Is.EqualTo(":" + new Guid(action1.m_Id))); // Unexpected when no longer existing
133200
}
134201

@@ -141,7 +208,8 @@ public void Actions_Reference_NameShouldReflectReferencedAction(Type typeToDelet
141208
// InputActionReference - Do not invalidate if action, action map or asset is deleted/destroyed. .action still returns the action.
142209
// (FIXED) InputActionAsset.RemoveActionMap - Do not remove actions within the map and they keep a stale reference to the removed map.
143210
// (FIXED) InputActionAsset.RemoveAction - Throws exception if action do not have any bindings.
144-
211+
// (FIXED) InputActionReference.Set(null) - Does not update m_Action. Hence .actions returns an incorrect reference. Also doesn't update ScriptableObject.name consistently with default ScriptableObject.
212+
// (FIXED) InputActionReference.Create - Returns null if action is null which is not inline with xmldoc description and inconsistent since it is allowed to create a reference and set it to null. If you want a null reference you should not call Create in the first place.
145213

146214
// TODO Make a test where action map is deleted
147215
// TODO Make a test where asset is deleted

Packages/com.unity.inputsystem/InputSystem/Actions/InputActionReference.cs

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ public InputAction action
7272
/// <see cref="InputActionMap"/> that is itself contained in an <see cref="InputActionAsset"/>.</exception>
7373
public void Set(InputAction action)
7474
{
75-
// TODO Throw if attempting to set reference on an input action reference that resides in an asset.
76-
7775
if (action == null)
7876
{
7977
m_Asset = null;
8078
m_ActionId = null;
79+
m_Action = null;
80+
name = string.Empty; // Scriptable object default name is empty string.
8181
return;
8282
}
8383

@@ -116,27 +116,27 @@ public void Set(InputActionAsset asset, string mapName, string actionName)
116116
if (actionMap == null)
117117
throw new ArgumentException($"No action map '{mapName}' in '{asset}'", nameof(mapName));
118118

119-
var action = actionMap.FindAction(actionName);
120-
if (action == null)
119+
var foundAction = actionMap.FindAction(actionName);
120+
if (foundAction == null)
121121
throw new ArgumentException($"No action '{actionName}' in map '{mapName}' of asset '{asset}'",
122122
nameof(actionName));
123123

124-
SetInternal(asset, action);
124+
SetInternal(asset, foundAction);
125125
}
126126

127-
private void SetInternal(InputActionAsset asset, InputAction action)
127+
private void SetInternal(InputActionAsset assetArg, InputAction actionArg)
128128
{
129-
var actionMap = action.actionMap;
130-
if (!asset.actionMaps.Contains(actionMap))
129+
var actionMap = actionArg.actionMap;
130+
if (!assetArg.actionMaps.Contains(actionMap))
131131
throw new ArgumentException(
132-
$"Action '{action}' is not contained in asset '{asset}'", nameof(action));
132+
$"Action '{actionArg}' is not contained in asset '{assetArg}'", nameof(actionArg));
133133

134134
// If we are setting the reference in edit-mode, we want the state to be reflected in the serialized
135135
// object and hence assign serialized fields. This is a destructive operation.
136-
m_Asset = asset;
137-
m_ActionId = action.id.ToString();
138-
name = GetDisplayName(action);
139-
m_Action = action;
136+
m_Asset = assetArg;
137+
m_ActionId = actionArg.id.ToString();
138+
m_Action = actionArg;
139+
name = GetDisplayName(actionArg);
140140

141141
////REVIEW: should this dirty the asset if IDs had not been generated yet?
142142
}
@@ -147,23 +147,19 @@ private void SetInternal(InputActionAsset asset, InputAction action)
147147
/// <returns>A string representation of the reference.</returns>
148148
public override string ToString()
149149
{
150-
try
151-
{
152-
var value = action;
150+
var value = action; // Indirect resolve
151+
if (value == null)
152+
return base.ToString();
153+
if (value.actionMap != null)
153154
return $"{m_Asset.name}:{value.actionMap.name}/{value.name}";
154-
}
155-
catch
156-
{
157-
if (m_Asset != null)
158-
return $"{m_Asset.name}:{m_ActionId}";
159-
}
160-
161-
return base.ToString();
155+
return $"{m_Asset.name}:{m_ActionId}";
162156
}
163157

164-
internal static string GetDisplayName(InputAction action)
158+
private static string GetDisplayName(InputAction action)
165159
{
166-
return !string.IsNullOrEmpty(action?.actionMap?.name) ? $"{action.actionMap?.name}/{action.name}" : action?.name;
160+
return !string.IsNullOrEmpty(action?.actionMap?.name)
161+
? $"{action.actionMap?.name}/{action.name}"
162+
: action?.name;
167163
}
168164

169165
/// <summary>
@@ -193,8 +189,6 @@ public static implicit operator InputAction(InputActionReference reference)
193189
/// <returns>A new InputActionReference referencing <paramref name="action"/>.</returns>
194190
public static InputActionReference Create(InputAction action)
195191
{
196-
if (action == null)
197-
return null;
198192
var reference = CreateInstance<InputActionReference>();
199193
reference.Set(action);
200194
return reference;
@@ -232,15 +226,17 @@ internal static void ResetCachedAction()
232226
/// </summary>
233227
[NonSerialized] private InputAction m_Action;
234228

235-
// Make annoying Microsoft code analyzer happy.
229+
/// <summary>
230+
/// Equivalent to <see cref="InputActionReference.action"/>.
231+
/// </summary>
236232
public InputAction ToInputAction()
237233
{
238234
return action;
239235
}
240236

241237
private InputAction ResolveAction()
242238
{
243-
return m_Asset == null ? null : m_Asset.FindAction(new Guid(m_ActionId));
239+
return m_Asset ? m_Asset.FindAction(new Guid(m_ActionId)) : null;
244240
}
245241

246242
// OnValidate() is stripped out from player builds (editor only).

0 commit comments

Comments
 (0)