244 lines
9.0 KiB
C#
244 lines
9.0 KiB
C#
#if UNITY_6000_7_OR_NEWER
|
|
using System.IO;
|
|
using System.Linq;
|
|
using NUnit.Framework;
|
|
using Unity.Pipeline.Commands;
|
|
using Unity.Pipeline.Editor;
|
|
using Unity.Pipeline.Editor.Commands;
|
|
using Unity.Pipeline.Runtime.Commands;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace Unity.Pipeline.Tests.Editor
|
|
{
|
|
/// <summary>
|
|
/// Tests for the visual-element capture commands. The selector resolver
|
|
/// (<see cref="VisualElementCaptureSupport.ResolveElement(VisualElement,string)"/>) is the
|
|
/// riskiest new logic and is GPU-independent, so it gets the most coverage; the
|
|
/// <c>capture_editor_element</c> command is covered for its validation/miss branches plus a
|
|
/// GPU-gated happy path.
|
|
/// </summary>
|
|
public class CaptureVisualElementCommandsTests
|
|
{
|
|
// -- Selector resolver -------------------------------------------------------------------
|
|
|
|
static VisualElement BuildTree()
|
|
{
|
|
// root
|
|
// #toolbar .toolbar
|
|
// #search Button .primary (direct child of toolbar)
|
|
// #content
|
|
// #title Label .big (disabled)
|
|
var root = new VisualElement { name = "root" };
|
|
|
|
var toolbar = new VisualElement { name = "toolbar" };
|
|
toolbar.AddToClassList("toolbar");
|
|
var search = new Button { name = "search" };
|
|
search.AddToClassList("primary");
|
|
toolbar.Add(search);
|
|
|
|
var content = new VisualElement { name = "content" };
|
|
var title = new Label { name = "title" };
|
|
title.AddToClassList("big");
|
|
title.SetEnabled(false); // sets the :disabled pseudo-state
|
|
content.Add(title);
|
|
|
|
root.Add(toolbar);
|
|
root.Add(content);
|
|
return root;
|
|
}
|
|
|
|
[Test]
|
|
public void Resolve_ById()
|
|
{
|
|
var root = BuildTree();
|
|
var e = VisualElementCaptureSupport.ResolveElement(root, "#search");
|
|
Assert.IsNotNull(e);
|
|
Assert.AreEqual("search", e.name);
|
|
}
|
|
|
|
[Test]
|
|
public void Resolve_ByClass()
|
|
{
|
|
var root = BuildTree();
|
|
var e = VisualElementCaptureSupport.ResolveElement(root, ".toolbar");
|
|
Assert.IsNotNull(e);
|
|
Assert.AreEqual("toolbar", e.name);
|
|
}
|
|
|
|
[Test]
|
|
public void Resolve_ByType()
|
|
{
|
|
var root = BuildTree();
|
|
var e = VisualElementCaptureSupport.ResolveElement(root, "Label");
|
|
Assert.IsNotNull(e);
|
|
Assert.AreEqual("title", e.name);
|
|
Assert.IsInstanceOf<Label>(e);
|
|
}
|
|
|
|
[Test]
|
|
public void Resolve_TypeWithNameAndClass()
|
|
{
|
|
var root = BuildTree();
|
|
var e = VisualElementCaptureSupport.ResolveElement(root, "Button#search.primary");
|
|
Assert.IsNotNull(e);
|
|
Assert.AreEqual("search", e.name);
|
|
Assert.IsInstanceOf<Button>(e);
|
|
}
|
|
|
|
[Test]
|
|
public void Resolve_DescendantChain()
|
|
{
|
|
var root = BuildTree();
|
|
var e = VisualElementCaptureSupport.ResolveElement(root, ".toolbar #search");
|
|
Assert.IsNotNull(e);
|
|
Assert.AreEqual("search", e.name);
|
|
}
|
|
|
|
[Test]
|
|
public void Resolve_ChildCombinator()
|
|
{
|
|
var root = BuildTree();
|
|
// search IS a direct child of toolbar.
|
|
Assert.IsNotNull(VisualElementCaptureSupport.ResolveElement(root, "#toolbar > #search"));
|
|
// title is NOT a direct child of toolbar, so the child combinator must miss.
|
|
Assert.IsNull(VisualElementCaptureSupport.ResolveElement(root, "#toolbar > #title"));
|
|
}
|
|
|
|
[Test]
|
|
public void Resolve_PseudoDisabledAndEnabled()
|
|
{
|
|
var root = BuildTree();
|
|
Assert.IsNotNull(VisualElementCaptureSupport.ResolveElement(root, "#title:disabled"),
|
|
"title is disabled, so :disabled should match");
|
|
Assert.IsNull(VisualElementCaptureSupport.ResolveElement(root, "#title:enabled"),
|
|
"title is disabled, so :enabled should not match");
|
|
Assert.IsNull(VisualElementCaptureSupport.ResolveElement(root, "#title:not(disabled)"),
|
|
":not(disabled) should not match a disabled element");
|
|
}
|
|
|
|
[Test]
|
|
public void Resolve_Miss_ReturnsNull()
|
|
{
|
|
var root = BuildTree();
|
|
Assert.IsNull(VisualElementCaptureSupport.ResolveElement(root, "#does-not-exist"));
|
|
}
|
|
|
|
[Test]
|
|
public void Resolve_InvalidSelector_ReturnsNull()
|
|
{
|
|
var root = BuildTree();
|
|
Assert.IsNull(VisualElementCaptureSupport.ResolveElement(root, "#"), "dangling '#' is invalid");
|
|
Assert.IsNull(VisualElementCaptureSupport.ResolveElement(root, ""), "empty selector");
|
|
}
|
|
|
|
// -- capture_editor_element command ------------------------------------------------------
|
|
|
|
[Test]
|
|
public void EditorCommand_IsDiscovered()
|
|
{
|
|
CommandRegistry.SetDiscovery(new TypeCacheCommandDiscovery());
|
|
var cmd = CommandRegistry.DiscoverCommands().FirstOrDefault(c => c.Name == "capture_editor_element");
|
|
Assert.IsNotNull(cmd, "capture_editor_element should be discovered");
|
|
Assert.IsTrue(cmd.MainThreadRequired);
|
|
}
|
|
|
|
[Test]
|
|
public void RuntimeCommand_IsDiscovered_AndRuntimeOnly()
|
|
{
|
|
CommandRegistry.SetDiscovery(new TypeCacheCommandDiscovery());
|
|
var cmd = CommandRegistry.DiscoverCommands().FirstOrDefault(c => c.Name == "capture_runtime_element");
|
|
Assert.IsNotNull(cmd, "capture_runtime_element should be discovered");
|
|
Assert.IsTrue(cmd.RuntimeOnly, "runtime capture command should be RuntimeOnly");
|
|
}
|
|
|
|
[Test]
|
|
public void EditorCommand_MissingWindow_Fails()
|
|
{
|
|
var result = CaptureEditorElementCommand.CaptureEditorElement(window: "", selector: "#x");
|
|
Assert.IsFalse(result.Success);
|
|
StringAssert.Contains("window", result.Message);
|
|
}
|
|
|
|
[Test]
|
|
public void EditorCommand_UnknownWindow_Fails()
|
|
{
|
|
var result = CaptureEditorElementCommand.CaptureEditorElement(
|
|
window: "ThisWindowTypeDoesNotExist12345", selector: "#x");
|
|
Assert.IsFalse(result.Success);
|
|
StringAssert.Contains("No open EditorWindow", result.Message);
|
|
}
|
|
|
|
[Test]
|
|
public void EditorCommand_SelectorMiss_Fails()
|
|
{
|
|
var win = ScriptableObject.CreateInstance<TestCaptureWindow>();
|
|
try
|
|
{
|
|
win.Show();
|
|
var result = CaptureEditorElementCommand.CaptureEditorElement(
|
|
window: nameof(TestCaptureWindow), selector: "#no-such-element");
|
|
Assert.IsFalse(result.Success);
|
|
StringAssert.Contains("No element matched", result.Message);
|
|
}
|
|
finally
|
|
{
|
|
win.Close();
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void EditorCommand_HappyPath_WritesPngAndBase64()
|
|
{
|
|
if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null)
|
|
Assert.Ignore("No GPU available; capture readback is not meaningful in this environment.");
|
|
|
|
var win = ScriptableObject.CreateInstance<TestCaptureWindow>();
|
|
var output = Path.Combine(Path.GetTempPath(), $"pipeline_ve_{System.Guid.NewGuid():N}.png");
|
|
try
|
|
{
|
|
win.Show();
|
|
var result = CaptureEditorElementCommand.CaptureEditorElement(
|
|
window: nameof(TestCaptureWindow), selector: "#target", output: output);
|
|
|
|
Assert.IsTrue(result.Success, result.Message);
|
|
Assert.AreEqual("png", result.Encoding);
|
|
Assert.Greater(result.Width, 0);
|
|
Assert.Greater(result.Height, 0);
|
|
Assert.IsFalse(string.IsNullOrEmpty(result.Base64), "base64 payload should be populated");
|
|
Assert.Greater(result.Bytes, 0);
|
|
Assert.AreEqual(output, result.Path);
|
|
FileAssert.Exists(output);
|
|
}
|
|
finally
|
|
{
|
|
win.Close();
|
|
if (File.Exists(output))
|
|
File.Delete(output);
|
|
}
|
|
}
|
|
|
|
/// <summary>A minimal EditorWindow with one named, sized element to capture.</summary>
|
|
class TestCaptureWindow : EditorWindow
|
|
{
|
|
void CreateGUI()
|
|
{
|
|
var target = new VisualElement
|
|
{
|
|
name = "target",
|
|
style =
|
|
{
|
|
width = 120,
|
|
height = 60,
|
|
backgroundColor = Color.red
|
|
}
|
|
};
|
|
rootVisualElement.Add(target);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|