Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,16 @@ jobs:
restore-keys: Library-${{ runner.os }}-${{ matrix.unityVersion }}-EditMode-

- name: Run EditMode tests
env:
LIBGL_ALWAYS_SOFTWARE: '1'
run: |
unity --non-interactive test . \
xvfb-run --auto-servernum --server-args="-screen 0 1280x1024x24" \
unity --non-interactive test . \
--editor-version "${{ matrix.unityVersion }}" \
--mode EditMode \
--output Logs/TestResults/editmode.xml \
--timeout 900 \
-- -nographics -logFile Logs/editmode.log
-- -force-glcore -logFile Logs/editmode.log

- name: Return Unity license
if: always() && env.UNITY_LICENSE_ACTIVATION_ATTEMPTED == 'true'
Expand Down
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ Use Unity CLI through `.github/actions/setup-unity`.

## How to run tests locally
### EditMode
Run tests through Unity CLI (which manages batch mode):
Run tests through Unity CLI (which manages batch mode). Inspector interaction and
rendering tests require graphics; run Linux tests under Xvfb as in CI:
```
unity --non-interactive test . --editor-version 2021.3.45f2 \
--mode EditMode --output Logs/TestResults/editmode.xml --timeout 900 \
-- -nographics -logFile Logs/editmode.log
-- -logFile Logs/editmode.log
```
Repeat with `--editor-version 2023.2.22f1` for the generic/variance feature gate.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using NUnit.Framework;

namespace MackySoft.SerializeReferenceExtensions.Tests
{
public sealed class AddTypeMenuAttributeTests
{
[Test]
public void MenuPath_ExposesSegmentsAndLeafName ()
{
var attribute = new AddTypeMenuAttribute("Commands/Movement/Run", -4);

Assert.That(attribute.GetSplittedMenuName(), Is.EqualTo(new[] { "Commands", "Movement", "Run" }));
Assert.That(attribute.GetTypeNameWithoutPath(), Is.EqualTo("Run"));
Assert.That(attribute.MenuName, Is.EqualTo("Commands/Movement/Run"));
Assert.That(attribute.Order, Is.EqualTo(-4));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#if UNITY_2023_2_OR_NEWER
using System;
using MackySoft.SerializeReferenceExtensions.Editor;
using NUnit.Framework;

namespace MackySoft.SerializeReferenceExtensions.Tests
{
public sealed class GenericValueTypeVarianceReproductionTests
{
// Known product defect: boxing does not make a variant interface conversion legal.
// Run explicitly until the product's compatibility policy is corrected.
[Test, Explicit("Known product defect: value-type arguments must remain invariant.")]
public void Covariance_DoesNotBoxValueTypeArguments ()
{
var candidates = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(ICovariant<object>));

Assert.That(candidates, !Does.Contain(typeof(IntProducer)));
}

[Test, Explicit("Known product defect: value-type arguments must remain invariant.")]
public void Contravariance_DoesNotBoxValueTypeArguments ()
{
var candidates = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(IContravariant<int>));

Assert.That(candidates, !Does.Contain(typeof(ObjectConsumer)));
}

[Serializable]
public sealed class IntProducer : ICovariant<int>
{
public int Create () => 0;
}

[Serializable]
public sealed class ObjectConsumer : IContravariant<object> { }
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;

namespace MackySoft.SerializeReferenceExtensions.Tests
{
internal static class InspectorDrawing
{
internal readonly struct DrawnContent
{
public string Text { get; }
public Rect Rect { get; }

public DrawnContent (string text, Rect rect)
{
Text = text;
Rect = rect;
}
}

public static DrawnContent[] Capture (SelectorInspectorWindow window)
{
// Unity has no public API for reading rendered IMGUI text. Adapt its GUI debugger
// here; package drawers and their registrations execute through PropertyField.
const BindingFlags flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
Assembly editorAssembly = typeof(EditorWindow).Assembly;
Type debugger = editorAssembly.GetType("UnityEditor.GUIViewDebuggerHelper", true);
FieldInfo parentField = typeof(EditorWindow).GetField("m_Parent", flags);
Assert.That(parentField, Is.Not.Null, "Unity GUI observation adapter: EditorWindow parent unavailable.");
object view = parentField.GetValue(window);
Assert.That(view, Is.Not.Null, "A graphics-capable Editor view is required for Inspector tests.");

debugger.GetMethod("DebugWindow", flags).Invoke(null, new[] { view });
try
{
window.DrawNow();
Type instructionType = editorAssembly.GetType("UnityEditor.IMGUIDrawInstruction", true);
Type listType = typeof(System.Collections.Generic.List<>).MakeGenericType(instructionType);
var instructions = (IList)Activator.CreateInstance(listType);
MethodInfo getInstructions = debugger.GetMethod("GetDrawInstructions", flags);
// Newer Editors add an optional stack-trace flag; it is irrelevant to text observation.
object[] arguments = getInstructions.GetParameters().Length == 1
? new object[] { instructions }
: new object[] { instructions, false };
getInstructions.Invoke(null, arguments);
FieldInfo contentField = instructionType.GetField("usedGUIContent", flags);
FieldInfo rectField = instructionType.GetField("rect", flags);
return instructions.Cast<object>()
.Select(item => new DrawnContent(((GUIContent)contentField.GetValue(item)).text, (Rect)rectField.GetValue(item)))
.Where(item => !string.IsNullOrEmpty(item.Text))
.ToArray();
}
finally
{
debugger.GetMethod("StopDebugging", flags).Invoke(null, null);
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using Asset = MackySoft.SerializeReferenceExtensions.Tests.SelectorTestAsset;

namespace MackySoft.SerializeReferenceExtensions.Tests
{
public sealed class ManagedReferenceInstanceMenuTests
{
private const string NewInstance = "New Instance";
private const string ResetInstance = "Reset and New Instance";
private Asset target;
private string assetPath;

[SetUp]
public void SetUp ()
{
target = ScriptableObject.CreateInstance<Asset>();
Undo.ClearAll();
}

[TearDown]
public void TearDown ()
{
Undo.ClearAll();
if (!string.IsNullOrEmpty(assetPath))
{
Assert.That(AssetDatabase.DeleteAsset(assetPath), Is.True);
assetPath = null;
}
else if (target != null)
{
UnityEngine.Object.DestroyImmediate(target);
}
}

[TestCase(NewInstance)]
[TestCase(ResetInstance)]
public void NullReference_DisablesInstanceCommands (string command)
{
using (var serialized = new SerializedObject(target))
{
GenericMenu menu = SerializedPropertyContextMenu.Open(serialized.FindProperty("reference"));
Assert.That(SerializedPropertyContextMenu.IsEnabled(menu, command), Is.False);
}
}

[TestCase("reference")]
[TestCase("parent.reference")]
[TestCase("array.Array.data[1]")]
[TestCase("list.Array.data[1]")]
public void NewInstance_ReplacesOnlySelectedReferenceWithAnIndependentInstance (string path)
{
var original = new Asset.FirstNode(37, "original");
var sibling = new Asset.FirstNode(52, "sibling");
target.array[0] = sibling;
target.list[0] = sibling;
using (var serialized = new SerializedObject(target))
{
serialized.FindProperty(path).managedReferenceValue = original;
serialized.ApplyModifiedPropertiesWithoutUndo();
SerializedPropertyContextMenu.Execute(SerializedPropertyContextMenu.Open(serialized.FindProperty(path)), NewInstance);
serialized.Update();
object replacement = serialized.FindProperty(path).managedReferenceValue;

Assert.That(replacement, Is.TypeOf<Asset.FirstNode>());
Assert.That(replacement, Is.Not.SameAs(original));
}
Assert.That(original.amount, Is.EqualTo(37));
Assert.That(original.Title, Is.EqualTo("original"));
Assert.That(target.array[0], Is.SameAs(sibling));
Assert.That(target.list[0], Is.SameAs(sibling));
Assert.That(target.unrelated, Is.EqualTo(81));
}

[Test]
public void ResetInstance_RestoresInitialValuesAndSupportsUndoRedoAndAssetReload ()
{
target.reference = new Asset.FirstNode(67, "edited");
assetPath = "Assets/__SelectorReset_" + Guid.NewGuid().ToString("N") + ".asset";
AssetDatabase.CreateAsset(target, assetPath);
AssetDatabase.SaveAssets();
Undo.ClearAll();
Undo.IncrementCurrentGroup();
object original = target.reference;

using (var serialized = new SerializedObject(target))
{
SerializedPropertyContextMenu.Execute(SerializedPropertyContextMenu.Open(serialized.FindProperty("reference")), ResetInstance);
}
Undo.FlushUndoRecordObjects();
AssertResetState();
Assert.That(target.reference, Is.Not.SameAs(original));

Undo.PerformUndo();
Assert.That(target.reference, Is.TypeOf<Asset.FirstNode>());
Assert.That(((Asset.FirstNode)target.reference).amount, Is.EqualTo(67));
Assert.That(((Asset.FirstNode)target.reference).Title, Is.EqualTo("edited"));
Undo.PerformRedo();
AssertResetState();

AssetDatabase.SaveAssets();
Resources.UnloadAsset(target);
target = AssetDatabase.LoadAssetAtPath<Asset>(assetPath);
Assert.That(target, Is.Not.Null);
AssertResetState();
}

private void AssertResetState ()
{
Assert.That(target.reference, Is.TypeOf<Asset.FirstNode>());
var node = (Asset.FirstNode)target.reference;
Assert.That(node.amount, Is.EqualTo(7));
Assert.That(node.Title, Is.EqualTo("first"));
Assert.That(node.payload.value, Is.EqualTo(8));
Assert.That(node.numbers, Is.EqualTo(new[] { 9 }));
Assert.That(target.unrelated, Is.EqualTo(81));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading