using System; using System.IO; using System.Linq; using System.Text; using AibisDream.FrameAnimation.Editor; using NUnit.Framework; using UnityEditor; using UnityEngine; namespace AibisDream.FrameAnimation.Tests.EditMode { public sealed class FrameClipStandaloneImportTests { private const string Root = "Assets/Tests/FrameAnimation/GeneratedStandaloneClipTests"; private const string TexturePath = Root + "/Atlas.png"; private const string JsonPath = Root + "/Atlas.json"; private const string ClipPath = Root + "/Standalone.asset"; [SetUp] public void SetUp() { AssetDatabase.DeleteAsset(Root); EnsureFolder(Root); } [TearDown] public void TearDown() { AssetDatabase.DeleteAsset(Root); AssetDatabase.Refresh(); } [Test] public void Preview_UsesOnlyFirstTagAndRespectsDirection() { var clip = CreateSourceClip(string.Empty, JsonWithTags("reverse")); var preview = FrameClipImportService.Preview(clip); Assert.That(preview.HasErrors, Is.False, JoinIssues(preview)); Assert.That(preview.SourceTagName, Is.EqualTo("First")); CollectionAssert.AreEqual(new[] { 1, 0 }, preview.SourceIndices); Assert.That(preview.SpriteDiffs.Count(diff => diff.Kind == FrameAnimationSpriteChangeKind.Added), Is.EqualTo(2)); } [Test] public void Preview_WithoutTagsUsesAllFrames() { var clip = CreateSourceClip("Existing", JsonWithoutTags()); var preview = FrameClipImportService.Preview(clip); Assert.That(preview.HasErrors, Is.False, JoinIssues(preview)); Assert.That(preview.SourceTagName, Is.Empty); CollectionAssert.AreEqual(new[] { 0, 1 }, preview.SourceIndices); } [Test] public void Apply_InitializesEmptyIdAndRefreshPreservesExistingId() { var clip = CreateSourceClip(string.Empty, JsonWithTags("forward")); var first = FrameClipImportService.Preview(clip); Assert.That(FrameClipImportService.Apply(first, true, out var error), Is.True, error); Assert.That(clip.Id, Is.EqualTo("First")); Assert.That(clip.DisplayName, Is.EqualTo("First")); Assert.That(clip.FrameCount, Is.EqualTo(2)); Assert.That(clip.StandaloneImportSource.LastImportedTagName, Is.EqualTo("First")); var serialized = new SerializedObject(clip); serialized.FindProperty("id").stringValue = "CustomId"; serialized.ApplyModifiedPropertiesWithoutUndo(); WriteJson(JsonWithTags("reverse")); var second = FrameClipImportService.Preview(clip); Assert.That(FrameClipImportService.Apply(second, true, out error), Is.True, error); Assert.That(clip.Id, Is.EqualTo("CustomId")); CollectionAssert.AreEqual(new[] { 1, 0 }, clip.Frames.Select(frame => frame.SourceIndex)); } [Test] public void Detach_PreservesFramesAndAllowsGraphReference() { var clip = CreateSourceClip("External", JsonWithoutTags()); var preview = FrameClipImportService.Preview(clip); Assert.That(FrameClipImportService.Apply(preview, true, out var error), Is.True, error); var frames = clip.Frames.ToArray(); var graph = ScriptableObject.CreateInstance(); graph.Configure("Graph", "Graph", Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), clip.Id); AssetDatabase.CreateAsset(graph, Root + "/Graph.asset"); Assert.That(FrameAnimationAssetOperations.AddExistingManualClip(graph, clip, out error), Is.True, error); Assert.That(FrameAnimationGraphValidator.Validate(graph).HasErrors, Is.False); FrameClipImportService.DetachSource(clip); Assert.That(clip.HasStandaloneImportSource, Is.False); CollectionAssert.AreEqual(frames, clip.Frames); } [Test] public void Preview_RejectsSecondWritableStandaloneOwnerForSameTexture() { CreateTexture(); WriteJson(JsonWithoutTags()); var first = CreateLinkedClipAt(ClipPath, "FirstOwner"); var second = CreateLinkedClipAt(Root + "/Second.asset", "SecondOwner"); var preview = FrameClipImportService.Preview(second); Assert.That(first.StandaloneImportSource.Texture, Is.SameAs(second.StandaloneImportSource.Texture)); Assert.That(preview.HasErrors, Is.True); Assert.That(preview.Issues.Any(issue => issue.Code == FrameAnimationImportIssueCode.TextureOwnershipConflict), Is.True); } private static FrameClip CreateSourceClip(string id, string json) { CreateTexture(); WriteJson(json); return CreateLinkedClipAt(ClipPath, id); } private static FrameClip CreateLinkedClipAt(string path, string id) { var clip = ScriptableObject.CreateInstance(); clip.Configure(id, string.Empty, Array.Empty()); AssetDatabase.CreateAsset(clip, path); Assert.That(FrameClipImportService.EnableSource(clip, out var error), Is.True, error); var serialized = new SerializedObject(clip); var source = serialized.FindProperty("standaloneImportSource"); source.FindPropertyRelative("texture").objectReferenceValue = AssetDatabase.LoadAssetAtPath(TexturePath); source.FindPropertyRelative("asepriteJson").objectReferenceValue = AssetDatabase.LoadAssetAtPath(JsonPath); source.FindPropertyRelative("manageSpriteSlicing").boolValue = true; serialized.ApplyModifiedPropertiesWithoutUndo(); AssetDatabase.SaveAssets(); return clip; } private static void CreateTexture() { var texture = new Texture2D(4, 2, TextureFormat.RGBA32, false); texture.SetPixels(Enumerable.Range(0, 8).Select(index => index < 4 ? Color.red : Color.green).ToArray()); texture.Apply(); File.WriteAllBytes(TexturePath, texture.EncodeToPNG()); UnityEngine.Object.DestroyImmediate(texture); AssetDatabase.ImportAsset(TexturePath, ImportAssetOptions.ForceSynchronousImport); } private static void WriteJson(string json) { File.WriteAllText(JsonPath, json, new UTF8Encoding(false)); AssetDatabase.ImportAsset(JsonPath, ImportAssetOptions.ForceSynchronousImport); } private static string JsonWithTags(string direction) { return Json("[{\"name\":\"First\",\"from\":0,\"to\":1,\"direction\":\"" + direction + "\"},{\"name\":\"Ignored\",\"from\":1,\"to\":1,\"direction\":\"invalid\"}]"); } private static string JsonWithoutTags() { return Json("[]"); } private static string Json(string tags) { return "{" + "\"frames\":{" + "\"FrameA\":{\"frame\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":100,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}," + "\"FrameB\":{\"frame\":{\"x\":2,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":200,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}}," + "\"meta\":{\"image\":\"Atlas.png\",\"size\":{\"w\":4,\"h\":2},\"frameTags\":" + tags + "}}"; } private static string JoinIssues(FrameClipImportPreview preview) { return string.Join("\n", preview.Issues.Select(issue => issue.Message)); } private static void EnsureFolder(string path) { var segments = path.Split('/'); var current = segments[0]; for (var index = 1; index < segments.Length; index++) { var next = current + "/" + segments[index]; if (!AssetDatabase.IsValidFolder(next)) AssetDatabase.CreateFolder(current, segments[index]); current = next; } } } }