Add Unity Pipeline package support
This commit is contained in:
+211
@@ -0,0 +1,211 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Editor.Commands.Animation;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for CLI-214 Group A (animation clips): create clip with frame rate + loop, set/get/remove
|
||||
/// float curves, the overwrite-not-duplicate behaviour, the destructive remove guard, dry_run, and
|
||||
/// the sandbox guard. Assets are generated in-test under a throwaway root and cleaned up.
|
||||
/// </summary>
|
||||
public class AnimationClipCommandsTests
|
||||
{
|
||||
private const string Root = "Assets/__CLI214AnimTest";
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
if (AssetDatabase.IsValidFolder(Root))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(Root);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectRef PathRef(string path) => new ObjectRef { Path = path };
|
||||
|
||||
private static JArray TwoKeys() => new JArray
|
||||
{
|
||||
new JObject { ["time"] = 0f, ["value"] = 0f },
|
||||
new JObject { ["time"] = 1f, ["value"] = 5f }
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void CreateAnimationClip_SetsFrameRateAndLoop()
|
||||
{
|
||||
var path = Root + "/Clips/Walk.anim";
|
||||
var result = AnimationClipCommands.CreateAnimationClip(path, frameRate: 30f, loop: true);
|
||||
|
||||
Assert.AreEqual(path, result.AssetPath);
|
||||
|
||||
var loaded = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
|
||||
Assert.IsNotNull(loaded, "Clip should exist on disk");
|
||||
Assert.AreEqual(30f, loaded.frameRate, 0.001f);
|
||||
|
||||
var info = AnimationClipCommands.GetAnimationClip(PathRef(path));
|
||||
Assert.AreEqual(30f, info.FrameRate, 0.001f);
|
||||
Assert.IsTrue(info.Loop, "loop should be reflected by get_animation_clip");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAnimationClip_OverwriteWithoutConfirm_Throws()
|
||||
{
|
||||
var path = Root + "/Clips/Dup.anim";
|
||||
AnimationClipCommands.CreateAnimationClip(path);
|
||||
|
||||
Assert.Throws<System.ArgumentException>(() => AnimationClipCommands.CreateAnimationClip(path));
|
||||
Assert.DoesNotThrow(() => AnimationClipCommands.CreateAnimationClip(path, confirm: true));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAnimationClip_OutOfProject_Throws()
|
||||
{
|
||||
Assert.Throws<System.ArgumentException>(() => AnimationClipCommands.CreateAnimationClip("../Outside.anim"));
|
||||
Assert.Throws<System.ArgumentException>(() => AnimationClipCommands.CreateAnimationClip("Assets/../Outside.anim"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAnimationClip_DryRun_WritesNothing()
|
||||
{
|
||||
var path = Root + "/Clips/DryRun.anim";
|
||||
var result = AnimationClipCommands.CreateAnimationClip(path, dryRun: true);
|
||||
|
||||
Assert.AreEqual(path, result.AssetPath);
|
||||
Assert.IsNull(AssetDatabase.LoadMainAssetAtPath(path), "dry_run must not write a clip");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetAnimationCurve_AddsCurveWithKeys()
|
||||
{
|
||||
var path = Root + "/Clips/Curve.anim";
|
||||
AnimationClipCommands.CreateAnimationClip(path);
|
||||
|
||||
var result = AnimationClipCommands.SetAnimationCurve(
|
||||
PathRef(path), path: "", type: "Transform", property: "m_LocalPosition.x", keys: TwoKeys());
|
||||
|
||||
Assert.AreEqual(2, result.KeyCount);
|
||||
Assert.AreEqual("m_LocalPosition.x", result.Binding.Property);
|
||||
|
||||
var info = AnimationClipCommands.GetAnimationClip(PathRef(path), includeKeys: true);
|
||||
Assert.AreEqual(1, info.Bindings.Count);
|
||||
var binding = info.Bindings[0];
|
||||
Assert.AreEqual("Transform", binding.Type);
|
||||
Assert.AreEqual("m_LocalPosition.x", binding.Property);
|
||||
Assert.AreEqual(2, binding.KeyCount);
|
||||
Assert.IsNotNull(binding.Keys);
|
||||
Assert.AreEqual(2, binding.Keys.Count);
|
||||
Assert.AreEqual(0f, binding.Keys[0].Value, 0.001f);
|
||||
Assert.AreEqual(5f, binding.Keys[1].Value, 0.001f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetAnimationCurve_ReplaceExisting_Overwrites()
|
||||
{
|
||||
var path = Root + "/Clips/Replace.anim";
|
||||
AnimationClipCommands.CreateAnimationClip(path);
|
||||
|
||||
AnimationClipCommands.SetAnimationCurve(PathRef(path), "", "Transform", "m_LocalPosition.x", TwoKeys());
|
||||
|
||||
var threeKeys = new JArray
|
||||
{
|
||||
new JObject { ["time"] = 0f, ["value"] = 0f },
|
||||
new JObject { ["time"] = 0.5f, ["value"] = 2f },
|
||||
new JObject { ["time"] = 1f, ["value"] = 4f }
|
||||
};
|
||||
var result = AnimationClipCommands.SetAnimationCurve(PathRef(path), "", "Transform", "m_LocalPosition.x", threeKeys);
|
||||
Assert.AreEqual(3, result.KeyCount);
|
||||
|
||||
var info = AnimationClipCommands.GetAnimationClip(PathRef(path));
|
||||
// Same binding => not duplicated; the single binding now has 3 keys.
|
||||
Assert.AreEqual(1, info.Bindings.Count, "Replacing a binding must not duplicate it");
|
||||
Assert.AreEqual(3, info.Bindings[0].KeyCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetAnimationCurve_DryRun_WritesNothing()
|
||||
{
|
||||
var path = Root + "/Clips/DryCurve.anim";
|
||||
AnimationClipCommands.CreateAnimationClip(path);
|
||||
|
||||
var result = AnimationClipCommands.SetAnimationCurve(
|
||||
PathRef(path), "", "Transform", "m_LocalPosition.x", TwoKeys(), dryRun: true);
|
||||
Assert.AreEqual(2, result.KeyCount);
|
||||
|
||||
var info = AnimationClipCommands.GetAnimationClip(PathRef(path));
|
||||
Assert.AreEqual(0, info.Bindings.Count, "dry_run must not write a curve");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveAnimationCurve_WithoutConfirm_Throws()
|
||||
{
|
||||
var path = Root + "/Clips/Remove.anim";
|
||||
AnimationClipCommands.CreateAnimationClip(path);
|
||||
AnimationClipCommands.SetAnimationCurve(PathRef(path), "", "Transform", "m_LocalPosition.x", TwoKeys());
|
||||
|
||||
Assert.Throws<System.ArgumentException>(
|
||||
() => AnimationClipCommands.RemoveAnimationCurve(PathRef(path), "", "Transform", "m_LocalPosition.x"),
|
||||
"remove without confirm should be rejected");
|
||||
|
||||
var info = AnimationClipCommands.GetAnimationClip(PathRef(path));
|
||||
Assert.AreEqual(1, info.Bindings.Count, "binding should survive a refused remove");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveAnimationCurve_WithConfirm_Removes()
|
||||
{
|
||||
var path = Root + "/Clips/Remove2.anim";
|
||||
AnimationClipCommands.CreateAnimationClip(path);
|
||||
AnimationClipCommands.SetAnimationCurve(PathRef(path), "", "Transform", "m_LocalPosition.x", TwoKeys());
|
||||
|
||||
AnimationClipCommands.RemoveAnimationCurve(PathRef(path), "", "Transform", "m_LocalPosition.x", confirm: true);
|
||||
|
||||
var info = AnimationClipCommands.GetAnimationClip(PathRef(path));
|
||||
Assert.AreEqual(0, info.Bindings.Count, "binding should be gone after a confirmed remove");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetAnimationCurve_UnknownType_Throws()
|
||||
{
|
||||
var path = Root + "/Clips/BadType.anim";
|
||||
AnimationClipCommands.CreateAnimationClip(path);
|
||||
|
||||
Assert.Throws<System.ArgumentException>(
|
||||
() => AnimationClipCommands.SetAnimationCurve(PathRef(path), "", "NotARealComponent__CLI214", "m_X", TwoKeys()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetAnimationCurve_ViaClient_Succeeds()
|
||||
{
|
||||
var path = Root + "/ViaClient/Curve.anim";
|
||||
AnimationClipCommands.CreateAnimationClip(path);
|
||||
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var response = server.Execute("set_animation_curve", new
|
||||
{
|
||||
clip = new { path },
|
||||
type = "Transform",
|
||||
property = "m_LocalPosition.x",
|
||||
keys = new object[]
|
||||
{
|
||||
new { time = 0f, value = 0f },
|
||||
new { time = 1f, value = 5f }
|
||||
}
|
||||
});
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"set_animation_curve should succeed: {response.Error}");
|
||||
}
|
||||
|
||||
var info = AnimationClipCommands.GetAnimationClip(PathRef(path));
|
||||
Assert.AreEqual(1, info.Bindings.Count);
|
||||
Assert.AreEqual(2, info.Bindings[0].KeyCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 613a4bb343db4a4cb96ae6fef8195425
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
using NUnit.Framework;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Editor.Commands.Animation;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for CLI-214 Group B (animator controllers): create controller (Base Layer, no states),
|
||||
/// add parameter (and duplicate => duplicate_parameter), add layer, add state (with motion + as
|
||||
/// default), add transition (with a matching condition), and the validation failures (missing
|
||||
/// parameter, mode/type mismatch). Assets are generated in-test and cleaned up.
|
||||
/// </summary>
|
||||
public class AnimatorControllerCommandsTests
|
||||
{
|
||||
private const string Root = "Assets/__CLI214CtrlTest";
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
if (AssetDatabase.IsValidFolder(Root))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(Root);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectRef PathRef(string path) => new ObjectRef { Path = path };
|
||||
|
||||
private string CreateController(string name)
|
||||
{
|
||||
var path = $"{Root}/{name}.controller";
|
||||
AnimatorControllerCommands.CreateAnimatorController(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateController_HasOneBaseLayerNoStates()
|
||||
{
|
||||
var path = CreateController("Empty");
|
||||
|
||||
var info = AnimatorControllerCommands.GetAnimatorController(PathRef(path));
|
||||
Assert.AreEqual(1, info.Layers.Count, "A new controller should have exactly one (Base) layer");
|
||||
Assert.AreEqual(0, info.Layers[0].States.Count, "Base Layer should start with no states");
|
||||
Assert.AreEqual(0, info.Parameters.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddParameter_Float_AndDuplicate()
|
||||
{
|
||||
var path = CreateController("Params");
|
||||
|
||||
var added = AnimatorControllerCommands.AddAnimatorParameter(PathRef(path), "Speed", "Float", new JValue(0f));
|
||||
Assert.IsInstanceOf<AddParameterResult>(added);
|
||||
|
||||
var info = AnimatorControllerCommands.GetAnimatorController(PathRef(path));
|
||||
Assert.AreEqual(1, info.Parameters.Count);
|
||||
Assert.AreEqual("Speed", info.Parameters[0].Name);
|
||||
Assert.AreEqual("Float", info.Parameters[0].ParamType);
|
||||
|
||||
// Duplicate name => structured error, not exception.
|
||||
var dup = AnimatorControllerCommands.AddAnimatorParameter(PathRef(path), "Speed", "Float");
|
||||
Assert.IsInstanceOf<ErrorResult>(dup);
|
||||
Assert.AreEqual("duplicate_parameter", ((ErrorResult)dup).Code);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddLayer_AppendsLayer()
|
||||
{
|
||||
var path = CreateController("Layers");
|
||||
|
||||
var added = AnimatorControllerCommands.AddAnimatorLayer(PathRef(path), "Upper", weight: 0.5f, blendingMode: "Additive");
|
||||
Assert.IsInstanceOf<AddLayerResult>(added);
|
||||
Assert.AreEqual(1, ((AddLayerResult)added).Layer.Index);
|
||||
|
||||
var info = AnimatorControllerCommands.GetAnimatorController(PathRef(path));
|
||||
Assert.AreEqual(2, info.Layers.Count);
|
||||
Assert.AreEqual("Upper", info.Layers[1].Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddState_WithMotionAndDefault()
|
||||
{
|
||||
var path = CreateController("States");
|
||||
|
||||
// A clip to use as the state motion.
|
||||
var clipPath = Root + "/Idle.anim";
|
||||
AnimationClipCommands.CreateAnimationClip(clipPath);
|
||||
|
||||
var added = AnimatorControllerCommands.AddAnimatorState(
|
||||
PathRef(path), layer: null, name: "Idle", motion: PathRef(clipPath), isDefault: true);
|
||||
Assert.IsInstanceOf<AddStateResult>(added);
|
||||
|
||||
var info = AnimatorControllerCommands.GetAnimatorController(PathRef(path));
|
||||
Assert.AreEqual(1, info.Layers[0].States.Count);
|
||||
var state = info.Layers[0].States[0];
|
||||
Assert.AreEqual("Idle", state.Name);
|
||||
Assert.IsTrue(state.IsDefault, "Idle should be the layer default");
|
||||
Assert.IsNotNull(state.Motion, "Idle should have a non-null motion");
|
||||
Assert.AreEqual("Idle", info.Layers[0].DefaultState);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddState_UnknownLayerName_ReturnsLayerNotFound()
|
||||
{
|
||||
var path = CreateController("BadLayer");
|
||||
|
||||
var result = AnimatorControllerCommands.AddAnimatorState(
|
||||
PathRef(path), layer: new JValue("NoSuchLayer"), name: "S");
|
||||
Assert.IsInstanceOf<ErrorResult>(result);
|
||||
Assert.AreEqual("layer_not_found", ((ErrorResult)result).Code);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddTransition_WithCondition()
|
||||
{
|
||||
var path = CreateController("Transitions");
|
||||
AnimatorControllerCommands.AddAnimatorParameter(PathRef(path), "Speed", "Float", new JValue(0f));
|
||||
AnimatorControllerCommands.AddAnimatorState(PathRef(path), null, "Idle");
|
||||
AnimatorControllerCommands.AddAnimatorState(PathRef(path), null, "Run");
|
||||
|
||||
var conditions = new JArray
|
||||
{
|
||||
new JObject { ["parameter"] = "Speed", ["mode"] = "Greater", ["threshold"] = 0.1f }
|
||||
};
|
||||
var added = AnimatorControllerCommands.AddAnimatorTransition(
|
||||
PathRef(path), null, "Idle", "Run", conditions);
|
||||
Assert.IsInstanceOf<AddTransitionResult>(added);
|
||||
Assert.AreEqual(1, ((AddTransitionResult)added).Transition.ConditionCount);
|
||||
|
||||
var info = AnimatorControllerCommands.GetAnimatorController(PathRef(path));
|
||||
var transitions = info.Layers[0].Transitions;
|
||||
Assert.AreEqual(1, transitions.Count);
|
||||
Assert.AreEqual("Idle", transitions[0].From);
|
||||
Assert.AreEqual("Run", transitions[0].To);
|
||||
Assert.AreEqual(1, transitions[0].Conditions.Count);
|
||||
Assert.AreEqual("Speed", transitions[0].Conditions[0].Parameter);
|
||||
Assert.AreEqual("Greater", transitions[0].Conditions[0].Mode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddTransition_UnknownParameter_ThrowsAndWritesNothing()
|
||||
{
|
||||
var path = CreateController("BadParam");
|
||||
AnimatorControllerCommands.AddAnimatorState(PathRef(path), null, "Idle");
|
||||
AnimatorControllerCommands.AddAnimatorState(PathRef(path), null, "Run");
|
||||
|
||||
var conditions = new JArray
|
||||
{
|
||||
new JObject { ["parameter"] = "Ghost", ["mode"] = "Greater", ["threshold"] = 0.1f }
|
||||
};
|
||||
|
||||
var ex = Assert.Throws<System.ArgumentException>(
|
||||
() => AnimatorControllerCommands.AddAnimatorTransition(PathRef(path), null, "Idle", "Run", conditions));
|
||||
StringAssert.Contains("Ghost", ex.Message, "error should name the offending parameter");
|
||||
|
||||
var info = AnimatorControllerCommands.GetAnimatorController(PathRef(path));
|
||||
Assert.AreEqual(0, info.Layers[0].Transitions.Count, "nothing should be written on a bad condition");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddTransition_ModeTypeMismatch_Throws()
|
||||
{
|
||||
var path = CreateController("Mismatch");
|
||||
AnimatorControllerCommands.AddAnimatorParameter(PathRef(path), "Speed", "Float", new JValue(0f));
|
||||
AnimatorControllerCommands.AddAnimatorState(PathRef(path), null, "Idle");
|
||||
AnimatorControllerCommands.AddAnimatorState(PathRef(path), null, "Run");
|
||||
|
||||
// "If" is only valid for Bool/Trigger, not a Float parameter.
|
||||
var conditions = new JArray
|
||||
{
|
||||
new JObject { ["parameter"] = "Speed", ["mode"] = "If" }
|
||||
};
|
||||
|
||||
Assert.Throws<System.ArgumentException>(
|
||||
() => AnimatorControllerCommands.AddAnimatorTransition(PathRef(path), null, "Idle", "Run", conditions));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddState_FractionalLayerIndex_ReturnsLayerNotFound()
|
||||
{
|
||||
var path = CreateController("FractionalLayer");
|
||||
|
||||
// 0.9 must NOT be silently rounded to layer 1; a non-integer index is rejected.
|
||||
var result = AnimatorControllerCommands.AddAnimatorState(
|
||||
PathRef(path), layer: new JValue(0.9d), name: "S");
|
||||
Assert.IsInstanceOf<ErrorResult>(result);
|
||||
Assert.AreEqual("layer_not_found", ((ErrorResult)result).Code);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddState_IntegerValuedFloatLayer_Resolves()
|
||||
{
|
||||
var path = CreateController("IntFloatLayer");
|
||||
|
||||
// 0.0 is an integer-valued float and resolves to the Base Layer (index 0).
|
||||
var added = AnimatorControllerCommands.AddAnimatorState(
|
||||
PathRef(path), layer: new JValue(0d), name: "Idle");
|
||||
Assert.IsInstanceOf<AddStateResult>(added);
|
||||
Assert.AreEqual(0, ((AddStateResult)added).State.Layer);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddTransition_ExitTimeOutOfRange_Throws()
|
||||
{
|
||||
var path = CreateController("BadExitTime");
|
||||
AnimatorControllerCommands.AddAnimatorState(PathRef(path), null, "Idle");
|
||||
AnimatorControllerCommands.AddAnimatorState(PathRef(path), null, "Run");
|
||||
|
||||
Assert.Throws<System.ArgumentException>(
|
||||
() => AnimatorControllerCommands.AddAnimatorTransition(
|
||||
PathRef(path), null, "Idle", "Run", conditions: null, hasExitTime: true, exitTime: 1.5f));
|
||||
|
||||
var info = AnimatorControllerCommands.GetAnimatorController(PathRef(path));
|
||||
Assert.AreEqual(0, info.Layers[0].Transitions.Count, "an out-of-range exitTime must write nothing");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddTransition_NegativeDuration_Throws()
|
||||
{
|
||||
var path = CreateController("BadDuration");
|
||||
AnimatorControllerCommands.AddAnimatorState(PathRef(path), null, "Idle");
|
||||
AnimatorControllerCommands.AddAnimatorState(PathRef(path), null, "Run");
|
||||
|
||||
Assert.Throws<System.ArgumentException>(
|
||||
() => AnimatorControllerCommands.AddAnimatorTransition(
|
||||
PathRef(path), null, "Idle", "Run", conditions: null, duration: -1f));
|
||||
|
||||
var info = AnimatorControllerCommands.GetAnimatorController(PathRef(path));
|
||||
Assert.AreEqual(0, info.Layers[0].Transitions.Count, "a negative duration must write nothing");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddParameter_DryRun_WritesNothing()
|
||||
{
|
||||
var path = CreateController("DryParam");
|
||||
|
||||
AnimatorControllerCommands.AddAnimatorParameter(PathRef(path), "Speed", "Float", new JValue(0f), dryRun: true);
|
||||
|
||||
var info = AnimatorControllerCommands.GetAnimatorController(PathRef(path));
|
||||
Assert.AreEqual(0, info.Parameters.Count, "dry_run must not write a parameter");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateController_ViaClient_Succeeds()
|
||||
{
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var path = Root + "/ViaClient/Made.controller";
|
||||
var response = server.Execute("create_animator_controller", new { path });
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"create_animator_controller should succeed: {response.Error}");
|
||||
Assert.IsNotNull(AssetDatabase.LoadMainAssetAtPath(path), "Controller should exist on disk");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1d78b8f19fc48cea840bc62c3204b3e
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Editor.Commands.Animation;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for CLI-214 Group C (Timeline). Timeline ships in the OPTIONAL com.unity.timeline package,
|
||||
/// so this fixture must COMPILE and PASS whether or not the package is installed:
|
||||
/// - <see cref="Timeline_NotInstalled_ReturnsPackageNotFound"/> runs only when the package is ABSENT
|
||||
/// and asserts the structured package_not_found error (no exception).
|
||||
/// - the create -> add track -> add clip -> get flow runs only when the package is PRESENT and
|
||||
/// calls <c>Assert.Pass</c> (not <c>Assert.Ignore</c>) when absent, so Yamato strict-mode
|
||||
/// agents see "Passed" rather than "Not Run" (Inconclusive) for the conditional skip.
|
||||
///
|
||||
/// All commands are reached through the public static methods; the Timeline types themselves are
|
||||
/// only touched via reflection inside the commands, never referenced by this test assembly.
|
||||
/// </summary>
|
||||
public class TimelineCommandsTests
|
||||
{
|
||||
private const string Root = "Assets/__CLI214TimelineTest";
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
if (AssetDatabase.IsValidFolder(Root))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(Root);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectRef PathRef(string path) => new ObjectRef { Path = path };
|
||||
|
||||
[Test]
|
||||
public void Timeline_NotInstalled_ReturnsPackageNotFound()
|
||||
{
|
||||
if (TimelineGuard.IsInstalled())
|
||||
Assert.Pass("com.unity.timeline is installed; the not-installed path is covered only when it is absent.");
|
||||
|
||||
var result = TimelineCommands.CreateTimeline(Root + "/T.playable");
|
||||
Assert.IsInstanceOf<ErrorResult>(result, "Timeline commands must not throw when the package is absent");
|
||||
Assert.AreEqual("package_not_found", ((ErrorResult)result).Code);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Timeline_CreateTrackClipGet_Flow()
|
||||
{
|
||||
if (!TimelineGuard.IsInstalled())
|
||||
Assert.Pass("com.unity.timeline is not installed; the Timeline authoring flow is validated only when the package is present.");
|
||||
|
||||
// create_timeline
|
||||
var timelinePath = Root + "/Cutscene.playable";
|
||||
var created = TimelineCommands.CreateTimeline(timelinePath, frameRate: 30f);
|
||||
Assert.IsInstanceOf<AuthoringResult>(created, "create_timeline should return an AuthoringResult when installed");
|
||||
|
||||
// add an Animation track
|
||||
var trackResult = TimelineCommands.AddTimelineTrack(PathRef(timelinePath), "Animation", name: "Anim");
|
||||
Assert.IsInstanceOf<AddTimelineTrackResult>(trackResult);
|
||||
|
||||
// a clip asset for the Animation track
|
||||
var clipPath = Root + "/Move.anim";
|
||||
AnimationClipCommands.CreateAnimationClip(clipPath);
|
||||
|
||||
// add_timeline_clip (start 0, duration 2)
|
||||
var clipResult = TimelineCommands.AddTimelineClip(
|
||||
PathRef(timelinePath), track: "Anim", start: 0f, duration: 2f, asset: PathRef(clipPath));
|
||||
Assert.IsInstanceOf<AddTimelineClipResult>(clipResult);
|
||||
|
||||
// get_timeline => one Animation track with one 2s clip
|
||||
var info = (TimelineInfo)TimelineCommands.GetTimeline(PathRef(timelinePath));
|
||||
Assert.AreEqual(1, info.Tracks.Count, "expected exactly one track");
|
||||
Assert.AreEqual("Animation", info.Tracks[0].TrackType);
|
||||
Assert.AreEqual(1, info.Tracks[0].Clips.Count, "expected exactly one clip");
|
||||
Assert.AreEqual(2d, info.Tracks[0].Clips[0].Duration, 0.001d);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 72d8c657533f4b8b96feb3af780d619a
|
||||
Reference in New Issue
Block a user