From ea3045ff56b426311378806cce2fa9d70d6a09bc Mon Sep 17 00:00:00 2001 From: Makihiro <13536348+mackysoft@users.noreply.github.com> Date: Fri, 16 Oct 2026 01:17:04 +0900 Subject: [PATCH 1/5] refactor(tests): move editor tests outside package distribution Why Keep development tests out of both UPM and unitypackage distributions. What Changed Move the existing test assembly and fixtures to Assets/Tests/SerializeReferenceExtensions, preserving their contents and GUIDs. Document the development test location and distribution boundary. Impact The development project and existing Linux EditMode matrix still discover the same tests. Runtime and Editor package code are unchanged. --- AGENTS.md | 4 +++- Assets/Tests.meta | 8 ++++++++ .../SerializeReferenceExtensions.meta} | 0 .../SerializeReferenceExtensions}/Editor.meta | 0 .../Editor/AdvancedTypePopupTests.cs | 0 .../Editor/AdvancedTypePopupTests.cs.meta | 0 .../Editor/DefaultIntrinsicTypePolicyTests.cs | 0 .../Editor/DefaultIntrinsicTypePolicyTests.cs.meta | 0 ...ySoft.SerializeReferenceExtensions.Tests.Editor.asmdef | 0 ....SerializeReferenceExtensions.Tests.Editor.asmdef.meta | 0 .../Editor/ManagedReferenceClipboardTestAsset.cs | 0 .../Editor/ManagedReferenceClipboardTestAsset.cs.meta | 0 .../Editor/ManagedReferenceContextualPropertyMenuTests.cs | 0 .../ManagedReferenceContextualPropertyMenuTests.cs.meta | 0 .../Editor/SerializedPropertyContextMenu.cs | 0 .../Editor/SerializedPropertyContextMenu.cs.meta | 0 .../Editor/TypeCandidateService_BaseTypeSelfTests.cs | 0 .../Editor/TypeCandidateService_BaseTypeSelfTests.cs.meta | 0 .../Editor/TypeCandidateService_GenericVarianceTests.cs | 0 .../TypeCandidateService_GenericVarianceTests.cs.meta | 0 .../Editor/TypeCandidateService_NonGenericTests.cs | 0 .../Editor/TypeCandidateService_NonGenericTests.cs.meta | 0 .../Editor/TypeCandidateTestTypes.cs | 0 .../Editor/TypeCandidateTestTypes.cs.meta | 0 24 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 Assets/Tests.meta rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests.meta => Tests/SerializeReferenceExtensions.meta} (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor.meta (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/AdvancedTypePopupTests.cs (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/AdvancedTypePopupTests.cs.meta (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/DefaultIntrinsicTypePolicyTests.cs (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/DefaultIntrinsicTypePolicyTests.cs.meta (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef.meta (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/ManagedReferenceClipboardTestAsset.cs (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/ManagedReferenceClipboardTestAsset.cs.meta (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/ManagedReferenceContextualPropertyMenuTests.cs (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/ManagedReferenceContextualPropertyMenuTests.cs.meta (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/SerializedPropertyContextMenu.cs (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/SerializedPropertyContextMenu.cs.meta (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/TypeCandidateService_BaseTypeSelfTests.cs (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/TypeCandidateService_BaseTypeSelfTests.cs.meta (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/TypeCandidateService_GenericVarianceTests.cs (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/TypeCandidateService_GenericVarianceTests.cs.meta (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/TypeCandidateService_NonGenericTests.cs (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/TypeCandidateService_NonGenericTests.cs.meta (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/TypeCandidateTestTypes.cs (100%) rename Assets/{MackySoft/MackySoft.SerializeReferenceExtensions/Tests => Tests/SerializeReferenceExtensions}/Editor/TypeCandidateTestTypes.cs.meta (100%) diff --git a/AGENTS.md b/AGENTS.md index c5e59e8..85f0971 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,9 @@ Primary risk area: type discovery & filtering (what types appear in the selector - Repository URL: `https://github.com/mackysoft/Unity-SerializeReferenceExtensions` - Package source: `Assets/MackySoft/MackySoft.SerializeReferenceExtensions` - Editor code: `.../Editor/**` -- Tests: `.../Tests/**` +- Tests: `Assets/Tests/SerializeReferenceExtensions/Editor/**` + +Keep test code, fixtures, and test assembly definitions under `Assets/Tests`, outside both the UPM package root and the `Assets/MackySoft` unitypackage export scope. ## Unity compatibility (critical) - Minimum supported Unity: 2021.3 (baseline for development/testing). diff --git a/Assets/Tests.meta b/Assets/Tests.meta new file mode 100644 index 0000000..b09a1fb --- /dev/null +++ b/Assets/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 608f572f0aa384abe9ea2013659fabb9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests.meta b/Assets/Tests/SerializeReferenceExtensions.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests.meta rename to Assets/Tests/SerializeReferenceExtensions.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor.meta b/Assets/Tests/SerializeReferenceExtensions/Editor.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor.meta rename to Assets/Tests/SerializeReferenceExtensions/Editor.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/AdvancedTypePopupTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/AdvancedTypePopupTests.cs similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/AdvancedTypePopupTests.cs rename to Assets/Tests/SerializeReferenceExtensions/Editor/AdvancedTypePopupTests.cs diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/AdvancedTypePopupTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/AdvancedTypePopupTests.cs.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/AdvancedTypePopupTests.cs.meta rename to Assets/Tests/SerializeReferenceExtensions/Editor/AdvancedTypePopupTests.cs.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/DefaultIntrinsicTypePolicyTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/DefaultIntrinsicTypePolicyTests.cs similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/DefaultIntrinsicTypePolicyTests.cs rename to Assets/Tests/SerializeReferenceExtensions/Editor/DefaultIntrinsicTypePolicyTests.cs diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/DefaultIntrinsicTypePolicyTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/DefaultIntrinsicTypePolicyTests.cs.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/DefaultIntrinsicTypePolicyTests.cs.meta rename to Assets/Tests/SerializeReferenceExtensions/Editor/DefaultIntrinsicTypePolicyTests.cs.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef b/Assets/Tests/SerializeReferenceExtensions/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef rename to Assets/Tests/SerializeReferenceExtensions/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef.meta rename to Assets/Tests/SerializeReferenceExtensions/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/ManagedReferenceClipboardTestAsset.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceClipboardTestAsset.cs similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/ManagedReferenceClipboardTestAsset.cs rename to Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceClipboardTestAsset.cs diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/ManagedReferenceClipboardTestAsset.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceClipboardTestAsset.cs.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/ManagedReferenceClipboardTestAsset.cs.meta rename to Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceClipboardTestAsset.cs.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/ManagedReferenceContextualPropertyMenuTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenuTests.cs similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/ManagedReferenceContextualPropertyMenuTests.cs rename to Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenuTests.cs diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/ManagedReferenceContextualPropertyMenuTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenuTests.cs.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/ManagedReferenceContextualPropertyMenuTests.cs.meta rename to Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenuTests.cs.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/SerializedPropertyContextMenu.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/SerializedPropertyContextMenu.cs similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/SerializedPropertyContextMenu.cs rename to Assets/Tests/SerializeReferenceExtensions/Editor/SerializedPropertyContextMenu.cs diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/SerializedPropertyContextMenu.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/SerializedPropertyContextMenu.cs.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/SerializedPropertyContextMenu.cs.meta rename to Assets/Tests/SerializeReferenceExtensions/Editor/SerializedPropertyContextMenu.cs.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_BaseTypeSelfTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_BaseTypeSelfTests.cs similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_BaseTypeSelfTests.cs rename to Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_BaseTypeSelfTests.cs diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_BaseTypeSelfTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_BaseTypeSelfTests.cs.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_BaseTypeSelfTests.cs.meta rename to Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_BaseTypeSelfTests.cs.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_GenericVarianceTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_GenericVarianceTests.cs similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_GenericVarianceTests.cs rename to Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_GenericVarianceTests.cs diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_GenericVarianceTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_GenericVarianceTests.cs.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_GenericVarianceTests.cs.meta rename to Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_GenericVarianceTests.cs.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_NonGenericTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_NonGenericTests.cs similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_NonGenericTests.cs rename to Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_NonGenericTests.cs diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_NonGenericTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_NonGenericTests.cs.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_NonGenericTests.cs.meta rename to Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_NonGenericTests.cs.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateTestTypes.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateTestTypes.cs similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateTestTypes.cs rename to Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateTestTypes.cs diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateTestTypes.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateTestTypes.cs.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateTestTypes.cs.meta rename to Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateTestTypes.cs.meta From d52ff39f265295d30451db87b1a3fa438a2eb811 Mon Sep 17 00:00:00 2001 From: Makihiro <13536348+mackysoft@users.noreply.github.com> Date: Fri, 16 Oct 2026 01:33:32 +0900 Subject: [PATCH 2/5] test(editor): cover managed reference inspector contracts Why Published selection, label, drawer and reset behavior lacked regression tests. What Changed Exercise public selection APIs and registered Inspector and menu operations. Cover inheritance and generic candidate gaps, and map tests to contract sources. Keep two value-type variance defect reproductions explicit until a product fix. Impact Editor behavior is checked without fixing private package implementation details. Existing clipboard tests and all product code remain unchanged. --- .../Editor/AddTypeMenuAttributeTests.cs | 18 ++ .../Editor/AddTypeMenuAttributeTests.cs.meta | 11 + ...nericValueTypeVarianceReproductionTests.cs | 38 ++++ ...ValueTypeVarianceReproductionTests.cs.meta | 11 + .../Editor/InspectorDrawing.cs | 63 ++++++ .../Editor/InspectorDrawing.cs.meta | 11 + .../ManagedReferenceInstanceMenuTests.cs | 121 +++++++++++ .../ManagedReferenceInstanceMenuTests.cs.meta | 11 + .../Editor/ManagedReferenceSelectionTests.cs | 131 ++++++++++++ .../ManagedReferenceSelectionTests.cs.meta | 11 + .../Editor/SelectorCustomPropertyDrawer.cs | 28 +++ .../SelectorCustomPropertyDrawer.cs.meta | 11 + .../Editor/SelectorInspectorWindow.cs | 60 ++++++ .../Editor/SelectorInspectorWindow.cs.meta | 11 + .../Editor/SelectorTestAsset.cs | 77 +++++++ .../Editor/SelectorTestAsset.cs.meta | 11 + .../Editor/SubclassSelectorInspectorTests.cs | 189 ++++++++++++++++++ .../SubclassSelectorInspectorTests.cs.meta | 11 + .../Editor/TypeCandidateContractTests.cs | 75 +++++++ .../Editor/TypeCandidateContractTests.cs.meta | 11 + .../TypeCandidateService_NonGenericTests.cs | 2 + .../Editor/TypePopupInteraction.cs | 40 ++++ .../Editor/TypePopupInteraction.cs.meta | 11 + Documentation/Tests.md | 94 +++++++++ 24 files changed, 1057 insertions(+) create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/AddTypeMenuAttributeTests.cs create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/AddTypeMenuAttributeTests.cs.meta create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/GenericValueTypeVarianceReproductionTests.cs create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/GenericValueTypeVarianceReproductionTests.cs.meta create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/InspectorDrawing.cs create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/InspectorDrawing.cs.meta create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceInstanceMenuTests.cs create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceInstanceMenuTests.cs.meta create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceSelectionTests.cs create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceSelectionTests.cs.meta create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/SelectorCustomPropertyDrawer.cs create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/SelectorCustomPropertyDrawer.cs.meta create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/SelectorInspectorWindow.cs create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/SelectorInspectorWindow.cs.meta create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/SelectorTestAsset.cs create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/SelectorTestAsset.cs.meta create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/SubclassSelectorInspectorTests.cs create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/SubclassSelectorInspectorTests.cs.meta create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateContractTests.cs create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateContractTests.cs.meta create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/TypePopupInteraction.cs create mode 100644 Assets/Tests/SerializeReferenceExtensions/Editor/TypePopupInteraction.cs.meta create mode 100644 Documentation/Tests.md diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/AddTypeMenuAttributeTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/AddTypeMenuAttributeTests.cs new file mode 100644 index 0000000..0e8ebfd --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/AddTypeMenuAttributeTests.cs @@ -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)); + } + } +} diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/AddTypeMenuAttributeTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/AddTypeMenuAttributeTests.cs.meta new file mode 100644 index 0000000..97fe96e --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/AddTypeMenuAttributeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ad4f1e44b4c494149895a6ae7449d375 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/GenericValueTypeVarianceReproductionTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/GenericValueTypeVarianceReproductionTests.cs new file mode 100644 index 0000000..77378f2 --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/GenericValueTypeVarianceReproductionTests.cs @@ -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)); + + 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)); + + Assert.That(candidates, !Does.Contain(typeof(ObjectConsumer))); + } + + [Serializable] + public sealed class IntProducer : ICovariant + { + public int Create () => 0; + } + + [Serializable] + public sealed class ObjectConsumer : IContravariant { } + } +} +#endif diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/GenericValueTypeVarianceReproductionTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/GenericValueTypeVarianceReproductionTests.cs.meta new file mode 100644 index 0000000..631f6d1 --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/GenericValueTypeVarianceReproductionTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ee11fd55fe3a46c9bc11e6b5d2c3cbb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/InspectorDrawing.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/InspectorDrawing.cs new file mode 100644 index 0000000..5f9c567 --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/InspectorDrawing.cs @@ -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() + .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); + } + } + } +} diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/InspectorDrawing.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/InspectorDrawing.cs.meta new file mode 100644 index 0000000..557e360 --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/InspectorDrawing.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 74d0bb2fd9dc84dbf920b65508dec1dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceInstanceMenuTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceInstanceMenuTests.cs new file mode 100644 index 0000000..dc5c688 --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceInstanceMenuTests.cs @@ -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(); + 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()); + 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()); + 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(assetPath); + Assert.That(target, Is.Not.Null); + AssertResetState(); + } + + private void AssertResetState () + { + Assert.That(target.reference, Is.TypeOf()); + 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)); + } + } +} diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceInstanceMenuTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceInstanceMenuTests.cs.meta new file mode 100644 index 0000000..ea062a7 --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceInstanceMenuTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7d701fc599352421187825a78724e88b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceSelectionTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceSelectionTests.cs new file mode 100644 index 0000000..4a8eced --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceSelectionTests.cs @@ -0,0 +1,131 @@ +using System; +using MackySoft.SerializeReferenceExtensions.Editor; +using NUnit.Framework; +using UnityEditor; +using UnityEngine; +using Asset = MackySoft.SerializeReferenceExtensions.Tests.SelectorTestAsset; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + public sealed class ManagedReferenceSelectionTests + { + private Asset target; + private string assetPath; + + [SetUp] + public void SetUp () + { + target = ScriptableObject.CreateInstance(); + } + + [TearDown] + public void TearDown () + { + Undo.ClearAll(); + if (!string.IsNullOrEmpty(assetPath)) + { + Assert.That(AssetDatabase.DeleteAsset(assetPath), Is.True); + assetPath = null; + } + else + { + UnityEngine.Object.DestroyImmediate(target); + } + } + + [Test] + public void SelectingAConcreteTypeForNull_CreatesAnInstance () + { + ChangeType("reference", typeof(Asset.FirstNode)); + + Assert.That(target.reference, Is.TypeOf()); + Assert.That(((Asset.FirstNode)target.reference).amount, Is.EqualTo(7)); + Assert.That(((Asset.FirstNode)target.reference).Title, Is.EqualTo("first")); + } + + [Test] + public void ChangingType_RestoresMatchingSerializedFields () + { + var original = new Asset.FirstNode(29, "user edited"); + target.reference = original; + + ChangeType("reference", typeof(Asset.SecondNode)); + + Assert.That(target.reference, Is.TypeOf()); + var changed = (Asset.SecondNode)target.reference; + Assert.That(changed.amount, Is.EqualTo(29)); + Assert.That(changed.Title, Is.EqualTo("user edited")); + Assert.That(changed.payload.value, Is.EqualTo(30)); + Assert.That(changed.numbers, Is.EqualTo(new[] { 31 })); + Assert.That(original.amount, Is.EqualTo(29)); + Assert.That(target.unrelated, Is.EqualTo(81)); + } + + [Test] + public void TypeChange_CanBeUndoneRedoneAndSavedForReload () + { + target.reference = new Asset.FirstNode(43, "saved selection"); + assetPath = "Assets/__SelectorSelection_" + Guid.NewGuid().ToString("N") + ".asset"; + AssetDatabase.CreateAsset(target, assetPath); + AssetDatabase.SaveAssets(); + Undo.ClearAll(); + Undo.IncrementCurrentGroup(); + + ChangeType("reference", typeof(Asset.SecondNode)); + Undo.FlushUndoRecordObjects(); + Assert.That(target.reference, Is.TypeOf()); + Undo.PerformUndo(); + Assert.That(target.reference, Is.TypeOf()); + Assert.That(((Asset.FirstNode)target.reference).amount, Is.EqualTo(43)); + Undo.PerformRedo(); + Assert.That(target.reference, Is.TypeOf()); + + AssetDatabase.SaveAssets(); + Resources.UnloadAsset(target); + target = AssetDatabase.LoadAssetAtPath(assetPath); + Assert.That(target, Is.Not.Null); + Assert.That(target.reference, Is.TypeOf()); + Assert.That(((Asset.SecondNode)target.reference).amount, Is.EqualTo(43)); + Assert.That(((Asset.SecondNode)target.reference).Title, Is.EqualTo("saved selection")); + Assert.That(target.unrelated, Is.EqualTo(81)); + } + + [TestCase("reference")] + [TestCase("parent.reference")] + [TestCase("array.Array.data[1]")] + [TestCase("list.Array.data[1]")] + public void SelectingAndClearingAProperty_ChangesOnlyThatReference (string path) + { + var untouched = new Asset.FirstNode(41, "untouched"); + target.array[0] = untouched; + target.list[0] = untouched; + + ChangeType(path, typeof(Asset.SecondNode)); + using (var serialized = new SerializedObject(target)) + { + Assert.That(serialized.FindProperty(path).managedReferenceValue, Is.TypeOf()); + } + ChangeType(path, null); + + using (var serialized = new SerializedObject(target)) + { + Assert.That(serialized.FindProperty(path).managedReferenceValue, Is.Null); + } + Assert.That(target.array.Length, Is.EqualTo(2)); + Assert.That(target.list.Count, Is.EqualTo(2)); + Assert.That(target.array[0], Is.SameAs(untouched)); + Assert.That(target.list[0], Is.SameAs(untouched)); + Assert.That(untouched.amount, Is.EqualTo(41)); + Assert.That(target.unrelated, Is.EqualTo(81)); + } + + private void ChangeType (string path, Type type) + { + using (var serialized = new SerializedObject(target)) + { + serialized.FindProperty(path).SetManagedReference(type); + serialized.ApplyModifiedProperties(); + } + } + } +} diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceSelectionTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceSelectionTests.cs.meta new file mode 100644 index 0000000..ff39794 --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/ManagedReferenceSelectionTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e3820fbbc83a456b86a8eb73f3b6d67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorCustomPropertyDrawer.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorCustomPropertyDrawer.cs new file mode 100644 index 0000000..cdec6d4 --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorCustomPropertyDrawer.cs @@ -0,0 +1,28 @@ +using UnityEditor; +using UnityEngine; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + [CustomPropertyDrawer(typeof(SelectorTestAsset.DrawnNode), true)] + public sealed class SelectorCustomPropertyDrawer : PropertyDrawer + { + public const string Marker = "Custom subtype editor"; + public const string EditButton = "Set subtype value"; + + public override float GetPropertyHeight (SerializedProperty property, GUIContent label) + { + return EditorGUIUtility.singleLineHeight * 4f; + } + + public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) + { + var header = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); + EditorGUI.LabelField(header, Marker); + var button = new Rect(position.x, position.yMax - EditorGUIUtility.singleLineHeight, position.width, EditorGUIUtility.singleLineHeight); + if (GUI.Button(button, EditButton)) + { + property.FindPropertyRelative("amount").intValue = 59; + } + } + } +} diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorCustomPropertyDrawer.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorCustomPropertyDrawer.cs.meta new file mode 100644 index 0000000..2a7dabf --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorCustomPropertyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 98d26b2eb63204dd4bdce0e3131efb39 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorInspectorWindow.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorInspectorWindow.cs new file mode 100644 index 0000000..9a87e6c --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorInspectorWindow.cs @@ -0,0 +1,60 @@ +using UnityEditor; +using UnityEngine; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + public sealed class SelectorInspectorWindow : EditorWindow + { + public const string FieldLabel = "Fixture field"; + public const string FollowingLabel = "Following field"; + private SerializedObject serializedTarget; + private string propertyPath; + private bool expanded; + + public void Initialize (UnityEngine.Object target, string path, bool isExpanded) + { + serializedTarget = new SerializedObject(target); + propertyPath = path; + expanded = isExpanded; + position = new Rect(100f, 100f, 600f, 300f); + } + + public void SetProperty (string path, bool isExpanded) + { + propertyPath = path; + expanded = isExpanded; + } + + public void DrawNow () + { + SendEvent(new Event { type = EventType.Layout }); + SendEvent(new Event { type = EventType.Repaint }); + } + + private void OnGUI () + { + if (serializedTarget == null || serializedTarget.targetObject == null) + { + return; + } + + serializedTarget.Update(); + SerializedProperty property = serializedTarget.FindProperty(propertyPath); + property.isExpanded = expanded; + var label = new GUIContent(FieldLabel); + var rect = new Rect(16f, 16f, position.width - 32f, EditorGUI.GetPropertyHeight(property, label, true)); + EditorGUI.PropertyField(rect, property, label, true); + EditorGUI.LabelField(new Rect(rect.x, rect.yMax + 8f, rect.width, EditorGUIUtility.singleLineHeight), FollowingLabel); + serializedTarget.ApplyModifiedProperties(); + } + + private void OnDisable () + { + if (serializedTarget != null) + { + serializedTarget.Dispose(); + serializedTarget = null; + } + } + } +} diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorInspectorWindow.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorInspectorWindow.cs.meta new file mode 100644 index 0000000..a1995df --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorInspectorWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10b5027535cdb414b9a5ab8b7ca99253 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorTestAsset.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorTestAsset.cs new file mode 100644 index 0000000..e24648c --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorTestAsset.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + public sealed class SelectorTestAsset : ScriptableObject + { + [SerializeReference, SubclassSelector] public INode reference; + [SerializeReference, SubclassSelector(UseToStringAsLabel = true)] public INode labelled; + [SerializeReference, SubclassSelector] public INode[] array = new INode[2]; + [SerializeReference, SubclassSelector] public List list = new List { null, null }; + [SerializeReference, SubclassSelector(UseToStringAsLabel = true)] public INode[] labelledArray = new INode[2]; + [SerializeReference, SubclassSelector(UseToStringAsLabel = true)] public List labelledList = new List { null, null }; + public Parent parent = new Parent(); + public int unrelated = 81; + + public interface INode { } + + [Serializable] + public sealed class Parent + { + [SerializeReference, SubclassSelector] public INode reference; + } + + [Serializable] + public sealed class Payload + { + public int value; + } + + [Serializable, AddTypeMenu("First Node")] + public sealed class FirstNode : INode + { + public int amount = 7; + [SerializeField] private string m_Title = "first"; + public Payload payload = new Payload { value = 8 }; + public int[] numbers = { 9 }; + + public string Title => m_Title; + + public FirstNode () { } + + public FirstNode (int value, string title) + { + amount = value; + m_Title = title; + payload.value = value + 1; + numbers[0] = value + 2; + } + + public override string ToString () => m_Title; + } + + [Serializable, AddTypeMenu("Commands/Renamed Node")] + public sealed class SecondNode : INode + { + public int amount = 11; + [SerializeField] private string m_Title = "second"; + public Payload payload = new Payload { value = 12 }; + public int[] numbers = { 13 }; + + public string Title => m_Title; + + public override string ToString () => m_Title; + } + + [Serializable] + public class DrawnNode : INode + { + public int amount; + } + + [Serializable] + public sealed class DerivedDrawnNode : DrawnNode { } + } +} diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorTestAsset.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorTestAsset.cs.meta new file mode 100644 index 0000000..bb66714 --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/SelectorTestAsset.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd79db74730df4ab48a41c586a137380 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/SubclassSelectorInspectorTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/SubclassSelectorInspectorTests.cs new file mode 100644 index 0000000..bd024bf --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/SubclassSelectorInspectorTests.cs @@ -0,0 +1,189 @@ +using System.Collections; +using System.Linq; +using MackySoft.SerializeReferenceExtensions.Editor; +using NUnit.Framework; +using UnityEditor; +using UnityEngine; +using UnityEngine.TestTools; +using Asset = MackySoft.SerializeReferenceExtensions.Tests.SelectorTestAsset; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + public sealed class SubclassSelectorInspectorTests + { + private Asset target; + private SelectorInspectorWindow window; + private EditorWindow popup; + + [UnitySetUp] + public IEnumerator SetUp () + { + target = ScriptableObject.CreateInstance(); + window = ScriptableObject.CreateInstance(); + window.Initialize(target, "reference", false); + window.ShowUtility(); + yield return null; + } + + [TearDown] + public void TearDown () + { + if (popup != null) + { + popup.Close(); + } + if (window != null) + { + window.Close(); + } + if (target != null) + { + UnityEngine.Object.DestroyImmediate(target); + } + } + + [UnityTest] + public IEnumerator DropdownSelection_ChangesTypePreservesValuesAndCanSelectNull () + { + target.reference = new Asset.SecondNode { amount = 46 }; + OpenPopup("Renamed Node"); + TypePopupInteraction.SelectVisibleItem(popup, "First Node"); + yield return null; + + Assert.That(target.reference, Is.TypeOf()); + Assert.That(((Asset.FirstNode)target.reference).amount, Is.EqualTo(46)); + Assert.That(((Asset.FirstNode)target.reference).Title, Is.EqualTo("second")); + Assert.That(Text(), Does.Contain("First Node")); + + OpenPopup("First Node"); + TypePopupInteraction.SelectVisibleItem(popup, ""); + yield return null; + + Assert.That(target.reference, Is.Null); + Assert.That(Text(), Does.Contain("")); + Assert.That(target.unrelated, Is.EqualTo(81)); + } + + [Test] + public void TypeLabel_UsesMenuLeafAndShowsNullAfterClearing () + { + target.reference = new Asset.SecondNode(); + Assert.That(Text(), Does.Contain("Renamed Node")); + Assert.That(Text(), Does.Contain(SelectorInspectorWindow.FieldLabel)); + + using (var serialized = new SerializedObject(target)) + { + serialized.FindProperty("reference").SetManagedReference(null); + serialized.ApplyModifiedProperties(); + } + Assert.That(Text(), Does.Contain("")); + Assert.That(Text(), Does.Not.Contain("Renamed Node")); + } + + [Test] + public void UseToStringAsLabel_DrawsCurrentInstanceTextAndFallsBackForNull () + { + window.SetProperty("labelled", false); + target.labelled = new Asset.FirstNode(20, "Player command"); + + Assert.That(Text(), Does.Contain("Player command")); + Assert.That(Text(), Does.Not.Contain(SelectorInspectorWindow.FieldLabel)); + + using (var serialized = new SerializedObject(target)) + { + serialized.FindProperty("labelled.m_Title").stringValue = "Edited command"; + serialized.ApplyModifiedProperties(); + } + Assert.That(Text(), Does.Contain("Edited command")); + Assert.That(Text(), Does.Not.Contain("Player command")); + + target.labelled = new Asset.FirstNode(30, "Updated command"); + Assert.That(Text(), Does.Contain("Updated command")); + Assert.That(Text(), Does.Not.Contain("Player command")); + + target.labelled = null; + Assert.That(Text(), Does.Contain(SelectorInspectorWindow.FieldLabel)); + Assert.That(Text(), Does.Contain("")); + } + + [TestCase("labelledArray")] + [TestCase("labelledList")] + public void CollectionElements_DrawTheirOwnInstanceLabels (string collection) + { + target.labelledArray[0] = new Asset.FirstNode(1, "First element"); + target.labelledArray[1] = new Asset.FirstNode(2, "Second element"); + target.labelledList[0] = new Asset.FirstNode(1, "First element"); + target.labelledList[1] = new Asset.FirstNode(2, "Second element"); + window.SetProperty(collection + ".Array.data[0]", false); + Assert.That(Text(), Does.Contain("First element")); + Assert.That(Text(), Does.Not.Contain("Second element")); + + window.SetProperty(collection + ".Array.data[1]", false); + Assert.That(Text(), Does.Contain("Second element")); + Assert.That(Text(), Does.Not.Contain("First element")); + } + + [TestCase(false)] + [TestCase(true)] + public void CustomSubtypeDrawer_RendersFitsAndEditsSelectedInstance (bool derived) + { + target.reference = derived ? new Asset.DerivedDrawnNode() : new Asset.DrawnNode(); + window.SetProperty("reference", true); + InspectorDrawing.DrawnContent[] contents = InspectorDrawing.Capture(window); + Assert.That(contents.Select(item => item.Text), Does.Contain(SelectorCustomPropertyDrawer.Marker)); + Assert.That(contents.Select(item => item.Text), Does.Contain(derived ? "Derived Drawn Node" : "Drawn Node")); + Rect button = contents.First(item => item.Text == SelectorCustomPropertyDrawer.EditButton).Rect; + Rect following = contents.First(item => item.Text == SelectorInspectorWindow.FollowingLabel).Rect; + Assert.That(button.yMax, Is.LessThanOrEqualTo(following.yMin), "The custom drawer must fit before the following field."); + + window.SendEvent(new Event { type = EventType.MouseDown, mousePosition = button.center, button = 0 }); + window.SendEvent(new Event { type = EventType.MouseUp, mousePosition = button.center, button = 0 }); + Assert.That(((Asset.DrawnNode)target.reference).amount, Is.EqualTo(59)); + + window.SetProperty("reference", false); + Assert.That(Text(), Does.Not.Contain(SelectorCustomPropertyDrawer.Marker)); + Assert.That(Text(), Does.Not.Contain(SelectorCustomPropertyDrawer.EditButton)); + } + + [Test] + public void ExpandedReference_DrawsSerializedChildrenBeforeFollowingField () + { + target.reference = new Asset.FirstNode(34, "child fields"); + window.SetProperty("reference", true); + InspectorDrawing.DrawnContent[] contents = InspectorDrawing.Capture(window); + if (!contents.Any(item => item.Text == "Value")) + { + Rect payload = contents.First(item => item.Text == "Payload").Rect; + window.SendEvent(new Event { type = EventType.MouseDown, mousePosition = payload.center, button = 0 }); + window.SendEvent(new Event { type = EventType.MouseUp, mousePosition = payload.center, button = 0 }); + contents = InspectorDrawing.Capture(window); + } + string[] labels = contents.Select(item => item.Text).ToArray(); + Assert.That(labels, Does.Contain("Amount")); + Assert.That(labels, Does.Contain("Title")); + Assert.That(labels, Does.Contain("Payload")); + Assert.That(labels, Does.Contain("Value")); + Assert.That(labels, Does.Contain("Numbers")); + Rect following = contents.First(item => item.Text == SelectorInspectorWindow.FollowingLabel).Rect; + foreach (string name in new[] { "Amount", "Title", "Payload", "Value", "Numbers" }) + { + Assert.That(contents.First(item => item.Text == name).Rect.yMax, Is.LessThanOrEqualTo(following.yMin), name); + } + } + + private string[] Text () + { + return InspectorDrawing.Capture(window).Select(item => item.Text).ToArray(); + } + + private void OpenPopup (string currentTypeLabel) + { + window.Focus(); + Rect button = InspectorDrawing.Capture(window).First(item => item.Text == currentTypeLabel).Rect; + window.SendEvent(new Event { type = EventType.MouseDown, mousePosition = button.center, button = 0 }); + popup = Resources.FindObjectsOfTypeAll() + .SingleOrDefault(candidate => candidate.GetType().Name == "AdvancedDropdownWindow"); + Assert.That(popup, Is.Not.Null, "Clicking the registered selector must open its dropdown."); + } + } +} diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/SubclassSelectorInspectorTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/SubclassSelectorInspectorTests.cs.meta new file mode 100644 index 0000000..3a78f6b --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/SubclassSelectorInspectorTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 72a41dd4b8c2a47bfb7d324ba91a4b3a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateContractTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateContractTests.cs new file mode 100644 index 0000000..5099c7f --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateContractTests.cs @@ -0,0 +1,75 @@ +using System; +using MackySoft.SerializeReferenceExtensions.Editor; +using NUnit.Framework; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + public sealed class TypeCandidateContractTests + { + [Test] + public void AbstractBase_OffersConcreteDescendantsAndExcludesUnrelatedTypes () + { + var candidates = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(AbstractCommand)); + + Assert.That(candidates, Does.Contain(typeof(ConcreteCommand))); + Assert.That(candidates, Does.Contain(typeof(IndirectCommand))); + Assert.That(candidates, !Does.Contain(typeof(AbstractCommand))); + Assert.That(candidates, !Does.Contain(typeof(HiddenCommand))); + Assert.That(candidates, !Does.Contain(typeof(PublicSerializableClass))); + } + +#if UNITY_2023_2_OR_NEWER + [Test] + public void ConstructedAbstractGenericBase_OffersOnlyMatchingNongenericDescendants () + { + var candidates = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(GenericCommand)); + + Assert.That(candidates, Does.Contain(typeof(IntCommand))); + Assert.That(candidates, Does.Contain(typeof(IndirectIntCommand))); + Assert.That(candidates, !Does.Contain(typeof(StringCommand))); + Assert.That(candidates, !Does.Contain(typeof(GenericCommand))); + Assert.That(candidates, !Does.Contain(typeof(OpenCommand<>))); + Assert.That(candidates, !Does.Contain(typeof(OpenCommand))); + } + + [Test] + public void InheritedGenericInterface_IsDiscoveredThroughItsNongenericParent () + { + var candidates = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(IInvariant)); + + Assert.That(candidates, Does.Contain(typeof(InheritedInterfaceCommand))); + Assert.That(candidates, !Does.Contain(typeof(StringInvariantCommand))); + } + + [Test] + public void CombinedVariance_RequiresBothInputAndOutputToBeCompatible () + { + var candidates = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(IConversion)); + + Assert.That(candidates, Does.Contain(typeof(CompatibleConversion))); + Assert.That(candidates, !Does.Contain(typeof(NarrowInputConversion))); + Assert.That(candidates, !Does.Contain(typeof(WideOutputConversion))); + } + + [Serializable] public abstract class GenericCommand { } + [Serializable] public sealed class IntCommand : GenericCommand { } + [Serializable] public abstract class IntBridge : GenericCommand { } + [Serializable] public sealed class IndirectIntCommand : IntBridge { } + [Serializable] public sealed class StringCommand : GenericCommand { } + [Serializable] public sealed class OpenCommand : GenericCommand { } + public interface IIntCommand : IInvariant { } + [Serializable] public sealed class InheritedInterfaceCommand : IIntCommand { } + [Serializable] public sealed class StringInvariantCommand : IInvariant { } + public interface IConversion { } + [Serializable] public sealed class CompatibleConversion : IConversion { } + [Serializable] public sealed class NarrowInputConversion : IConversion { } + [Serializable] public sealed class WideOutputConversion : IConversion { } +#endif + + [Serializable] public abstract class AbstractCommand { } + [Serializable] public sealed class ConcreteCommand : AbstractCommand { } + [Serializable] public abstract class IntermediateCommand : AbstractCommand { } + [Serializable] public sealed class IndirectCommand : IntermediateCommand { } + [Serializable, HideInTypeMenu] public sealed class HiddenCommand : AbstractCommand { } + } +} diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateContractTests.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateContractTests.cs.meta new file mode 100644 index 0000000..5aaab1c --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateContractTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1f0c8b641d2c9495abf864774af69fb4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_NonGenericTests.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_NonGenericTests.cs index ea28b68..7ea72c8 100644 --- a/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_NonGenericTests.cs +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/TypeCandidateService_NonGenericTests.cs @@ -30,6 +30,8 @@ public static IEnumerable Cases () yield return new TestCaseData(typeof(HiddenSerializableClass), false).SetName("Candidates_ITestBase_HideInTypeMenu_NG"); yield return new TestCaseData(typeof(UnityObjectDerived), false).SetName("Candidates_ITestBase_UnityObjectDerived_NG"); yield return new TestCaseData(typeof(GenericCandidate), false).SetName("Candidates_ITestBase_GenericClosed_NG"); + yield return new TestCaseData(typeof(GenericCandidate<>), false).SetName("Candidates_ITestBase_GenericOpen_NG"); + yield return new TestCaseData(typeof(ConcreteDerivedType), false).SetName("Candidates_ITestBase_Unrelated_NG"); } [TestCaseSource(nameof(Cases))] diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/TypePopupInteraction.cs b/Assets/Tests/SerializeReferenceExtensions/Editor/TypePopupInteraction.cs new file mode 100644 index 0000000..8f91df7 --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/TypePopupInteraction.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq; +using System.Reflection; +using NUnit.Framework; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEngine; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + internal static class TypePopupInteraction + { + public static void SelectVisibleItem (EditorWindow popup, string label) + { + // Unity keeps dropdown rows and the keyboard selection internal. Read them only + // to navigate the real popup with keys; package selection callbacks run normally. + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + var tree = (AdvancedDropdownItem)popup.GetType().GetField("m_CurrentlyRenderedTree", flags).GetValue(popup); + object state = popup.GetType().GetField("m_State", flags).GetValue(popup); + MethodInfo selectedIndex = state.GetType().GetMethod("GetSelectedIndex", flags); +#if UNITY_6000_5_OR_NEWER + AdvancedDropdownItem[] rows = tree.childList.ToArray(); +#else + AdvancedDropdownItem[] rows = tree.children.ToArray(); +#endif + int targetIndex = Array.FindIndex(rows, row => row.name == label); + Assert.That(targetIndex, Is.GreaterThanOrEqualTo(0), "Visible popup item missing: " + label); + for (int step = 0; step <= rows.Length; step++) + { + if ((int)selectedIndex.Invoke(state, new object[] { tree }) == targetIndex) + { + popup.SendEvent(new Event { type = EventType.KeyDown, keyCode = KeyCode.Return }); + return; + } + popup.SendEvent(new Event { type = EventType.KeyDown, keyCode = KeyCode.DownArrow }); + } + Assert.Fail("Keyboard navigation did not reach popup item: " + label); + } + } +} diff --git a/Assets/Tests/SerializeReferenceExtensions/Editor/TypePopupInteraction.cs.meta b/Assets/Tests/SerializeReferenceExtensions/Editor/TypePopupInteraction.cs.meta new file mode 100644 index 0000000..0455591 --- /dev/null +++ b/Assets/Tests/SerializeReferenceExtensions/Editor/TypePopupInteraction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f22f40edb09c49b48bee62215b1c8d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation/Tests.md b/Documentation/Tests.md new file mode 100644 index 0000000..02c7e21 --- /dev/null +++ b/Documentation/Tests.md @@ -0,0 +1,94 @@ +# Specification tests + +The EditMode suite exercises the package through its public editor APIs, registered +property drawers and registered context-menu commands. Tests live in +`Assets/Tests/SerializeReferenceExtensions/Editor`, outside both the UPM package and +the `Assets/MackySoft` unitypackage export tree. + +## Contract sources + +- [README](../README.md): supported features, type eligibility, generic version + gates, collection elements and clipboard semantics. +- [Runtime attributes](../Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Runtime): + `SubclassSelector`, `AddTypeMenu` and `HideInTypeMenu`, including the documented + menu-path helpers. +- [Accepted UseToStringAsLabel fix (#99)](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/pull/99): + the instance string is the visible field label, including collection elements. + The override must affect the label actually drawn by Unity. +- [Release 1.3.0](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/releases/tag/1.3.0): + context-menu commands for initializing and resetting managed-reference instances. +- Unity [2021.3 SerializeReference](https://docs.unity3d.com/2021.3/Documentation/ScriptReference/SerializeReference.html) + and [2023.2 SerializeReference](https://docs.unity3d.com/2023.2/Documentation/ScriptReference/SerializeReference.html): + compatible serializable reference objects, null, nested references and collection + elements. Unity owns the underlying serialization; tests exercise package edits + before observing Undo or saved assets. +- [C# variance](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance): + reference conversions for `in` and `out`; value-type arguments remain invariant. + +The implementation baseline for this work is merged commit `9587592`. Tests for +these established features require no product implementation changes. Ongoing +TriInspector/xNode compatibility work and proposed UI Toolkit changes are outside +this baseline. + +## Detection responsibilities + +| User-visible contract | Existing tests retained | Additional responsibility | +| --- | --- | --- | +| Eligible public and nested implementations; hidden, abstract, nonserializable, Unity object and generic candidates excluded | `TypeCandidateService_NonGenericTests`, `DefaultIntrinsicTypePolicyTests` | Open generic candidates, unrelated implementations and abstract-base inheritance | +| Concrete field type itself can be selected | `TypeCandidateService_BaseTypeSelfTests` | Retain existing coverage | +| Generic field compatibility on Unity 2023.2+ | `TypeCandidateService_GenericVarianceTests` | Abstract generic bases, inherited interfaces and combined input/output variance | +| `AddTypeMenu` paths, names and ordering; null item; selection events | `AdvancedTypePopupTests` | Public attribute path helpers and displayed Inspector labels | +| Selecting, replacing or clearing one managed reference | No value-transition tests | Real dropdown input, concrete type, matching serialized values, null, nested fields, array/List elements, unaffected siblings, Undo/Redo and asset reload | +| `UseToStringAsLabel` | No regression test for #99 | Actual drawn text, fresh instance values, null fallback and collection labels | +| Custom subtype property drawers | No tests | Registered drawer output, height and editing through `EditorGUI.PropertyField` | +| Copy / Paste Values Only / Paste Type and Values | `ManagedReferenceContextualPropertyMenuTests` | Retain #107 coverage, including independent snapshots, availability, pending edits, Undo/Redo and asset reload | +| New Instance / Reset and New Instance | No tests | Registered menu availability, independent replacement, reset to initial values, Undo/Redo and asset reload | + +Expected values come from those sources and literal fixture inputs. Tests compare +observable types, fields, menu availability and drawn text. They do not assert +private package members, cache contents, reflection scans or callback order. +Unity menu/GUI adapters bridge observation APIs that Unity does not expose publicly; +they invoke the package's normal registrations rather than private package methods. + +## Boundaries and remaining work + +- Fuzzy search is supplied by Unity's `AdvancedDropdown`. Menu contents and + selection are tested; search ranking is not a package guarantee. +- Generic candidate construction, whole-parent/whole-collection clipboard commands, + third-party inspectors and a UI Toolkit implementation are outside the adopted + feature contract. +- Boxed structs are described as version-dependent in the README. Existing discovery + tests are retained; they do not establish a serialization guarantee for structs. +- Clipboard survival across an actual script reload, prefab override persistence, + missing-type recovery and type-renaming migration need dedicated session fixtures. +- Mixed-value labels and multi-object dropdown edits are not exercised by the + single-object Inspector fixture. Clipboard multi-selection availability has + existing coverage. +- Value-type variance has a confirmed product defect in the baseline. Both cases + in `GenericValueTypeVarianceReproductionTests` fail their candidate-exclusion + assertions on Unity 2023.2.22f1. They are `Explicit` reproductions, skipped in + the continuous suite, and do not establish passing coverage for that contract. + +`New Instance` tests guarantee same-type independent replacement, without fixing +its value-copying details. The reset command is checked against literal initial +fixture values, and against a different edited state through Undo/Redo and reload. + +## Running + +Use the versions and commands in [AGENTS.md](../AGENTS.md). Inspector tests require +a graphics-capable Editor. The Linux CI matrix uses a virtual display and software +OpenGL for Unity 2021.3.45f2 and 2023.2.22f1, and its final `Verification` job requires +both applicable EditMode jobs to succeed. + +To reproduce the known covariance defect explicitly: + +```sh +unity --non-interactive test . --editor-version 2023.2.22f1 --mode EditMode \ + --filter MackySoft.SerializeReferenceExtensions.Tests.GenericValueTypeVarianceReproductionTests.Covariance_DoesNotBoxValueTypeArguments \ + --output Logs/TestResults/value-variance.xml --timeout 900 \ + -- -logFile Logs/value-variance.log +``` + +Use `Contravariance_DoesNotBoxValueTypeArguments` as the method name for the other +case. An assertion failure is the current expected reproduction result; after a +product fix, remove `Explicit` and include these exclusions in ordinary CI. From 15d884433d33439180f79ef2f5bd8a954493268b Mon Sep 17 00:00:00 2001 From: Makihiro <13536348+mackysoft@users.noreply.github.com> Date: Fri, 16 Oct 2026 01:33:33 +0900 Subject: [PATCH 3/5] ci(tests): provide graphics for inspector specification tests Why Rendered labels and real popup input require a graphics-capable Editor view. What Changed Run both Linux EditMode versions under Xvfb with software OpenGL. Document graphics requirements and the contract-to-test map. Impact Inspector regressions participate in the normal Verification gate. Player builds and package exports remain in the Release workflow. --- .github/workflows/tests.yaml | 7 +++++-- AGENTS.md | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 073b7c0..842b6a8 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -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' diff --git a/AGENTS.md b/AGENTS.md index 85f0971..89b7949 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,13 +31,16 @@ 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. +The contract-to-test map and known uncovered conditions are in +`Documentation/Tests.md`. ## Architecture guardrails - Runtime surface area should remain minimal (mainly attributes / data structures). From f0901e08235189f52eb579b3eeea6eb069173a8d Mon Sep 17 00:00:00 2001 From: Makihiro <13536348+mackysoft@users.noreply.github.com> Date: Fri, 16 Oct 2026 02:55:30 +0900 Subject: [PATCH 4/5] docs(tests): remove redundant test inventory Why A separate test inventory duplicates the executable tests and drifts over time. What Changed Remove Documentation/Tests.md and its AGENTS.md reference. Impact Test coverage and execution stay defined by the tests and CI configuration. --- AGENTS.md | 2 - Documentation/Tests.md | 94 ------------------------------------------ 2 files changed, 96 deletions(-) delete mode 100644 Documentation/Tests.md diff --git a/AGENTS.md b/AGENTS.md index 89b7949..b8446b3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,8 +39,6 @@ unity --non-interactive test . --editor-version 2021.3.45f2 \ -- -logFile Logs/editmode.log ``` Repeat with `--editor-version 2023.2.22f1` for the generic/variance feature gate. -The contract-to-test map and known uncovered conditions are in -`Documentation/Tests.md`. ## Architecture guardrails - Runtime surface area should remain minimal (mainly attributes / data structures). diff --git a/Documentation/Tests.md b/Documentation/Tests.md deleted file mode 100644 index 02c7e21..0000000 --- a/Documentation/Tests.md +++ /dev/null @@ -1,94 +0,0 @@ -# Specification tests - -The EditMode suite exercises the package through its public editor APIs, registered -property drawers and registered context-menu commands. Tests live in -`Assets/Tests/SerializeReferenceExtensions/Editor`, outside both the UPM package and -the `Assets/MackySoft` unitypackage export tree. - -## Contract sources - -- [README](../README.md): supported features, type eligibility, generic version - gates, collection elements and clipboard semantics. -- [Runtime attributes](../Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Runtime): - `SubclassSelector`, `AddTypeMenu` and `HideInTypeMenu`, including the documented - menu-path helpers. -- [Accepted UseToStringAsLabel fix (#99)](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/pull/99): - the instance string is the visible field label, including collection elements. - The override must affect the label actually drawn by Unity. -- [Release 1.3.0](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/releases/tag/1.3.0): - context-menu commands for initializing and resetting managed-reference instances. -- Unity [2021.3 SerializeReference](https://docs.unity3d.com/2021.3/Documentation/ScriptReference/SerializeReference.html) - and [2023.2 SerializeReference](https://docs.unity3d.com/2023.2/Documentation/ScriptReference/SerializeReference.html): - compatible serializable reference objects, null, nested references and collection - elements. Unity owns the underlying serialization; tests exercise package edits - before observing Undo or saved assets. -- [C# variance](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance): - reference conversions for `in` and `out`; value-type arguments remain invariant. - -The implementation baseline for this work is merged commit `9587592`. Tests for -these established features require no product implementation changes. Ongoing -TriInspector/xNode compatibility work and proposed UI Toolkit changes are outside -this baseline. - -## Detection responsibilities - -| User-visible contract | Existing tests retained | Additional responsibility | -| --- | --- | --- | -| Eligible public and nested implementations; hidden, abstract, nonserializable, Unity object and generic candidates excluded | `TypeCandidateService_NonGenericTests`, `DefaultIntrinsicTypePolicyTests` | Open generic candidates, unrelated implementations and abstract-base inheritance | -| Concrete field type itself can be selected | `TypeCandidateService_BaseTypeSelfTests` | Retain existing coverage | -| Generic field compatibility on Unity 2023.2+ | `TypeCandidateService_GenericVarianceTests` | Abstract generic bases, inherited interfaces and combined input/output variance | -| `AddTypeMenu` paths, names and ordering; null item; selection events | `AdvancedTypePopupTests` | Public attribute path helpers and displayed Inspector labels | -| Selecting, replacing or clearing one managed reference | No value-transition tests | Real dropdown input, concrete type, matching serialized values, null, nested fields, array/List elements, unaffected siblings, Undo/Redo and asset reload | -| `UseToStringAsLabel` | No regression test for #99 | Actual drawn text, fresh instance values, null fallback and collection labels | -| Custom subtype property drawers | No tests | Registered drawer output, height and editing through `EditorGUI.PropertyField` | -| Copy / Paste Values Only / Paste Type and Values | `ManagedReferenceContextualPropertyMenuTests` | Retain #107 coverage, including independent snapshots, availability, pending edits, Undo/Redo and asset reload | -| New Instance / Reset and New Instance | No tests | Registered menu availability, independent replacement, reset to initial values, Undo/Redo and asset reload | - -Expected values come from those sources and literal fixture inputs. Tests compare -observable types, fields, menu availability and drawn text. They do not assert -private package members, cache contents, reflection scans or callback order. -Unity menu/GUI adapters bridge observation APIs that Unity does not expose publicly; -they invoke the package's normal registrations rather than private package methods. - -## Boundaries and remaining work - -- Fuzzy search is supplied by Unity's `AdvancedDropdown`. Menu contents and - selection are tested; search ranking is not a package guarantee. -- Generic candidate construction, whole-parent/whole-collection clipboard commands, - third-party inspectors and a UI Toolkit implementation are outside the adopted - feature contract. -- Boxed structs are described as version-dependent in the README. Existing discovery - tests are retained; they do not establish a serialization guarantee for structs. -- Clipboard survival across an actual script reload, prefab override persistence, - missing-type recovery and type-renaming migration need dedicated session fixtures. -- Mixed-value labels and multi-object dropdown edits are not exercised by the - single-object Inspector fixture. Clipboard multi-selection availability has - existing coverage. -- Value-type variance has a confirmed product defect in the baseline. Both cases - in `GenericValueTypeVarianceReproductionTests` fail their candidate-exclusion - assertions on Unity 2023.2.22f1. They are `Explicit` reproductions, skipped in - the continuous suite, and do not establish passing coverage for that contract. - -`New Instance` tests guarantee same-type independent replacement, without fixing -its value-copying details. The reset command is checked against literal initial -fixture values, and against a different edited state through Undo/Redo and reload. - -## Running - -Use the versions and commands in [AGENTS.md](../AGENTS.md). Inspector tests require -a graphics-capable Editor. The Linux CI matrix uses a virtual display and software -OpenGL for Unity 2021.3.45f2 and 2023.2.22f1, and its final `Verification` job requires -both applicable EditMode jobs to succeed. - -To reproduce the known covariance defect explicitly: - -```sh -unity --non-interactive test . --editor-version 2023.2.22f1 --mode EditMode \ - --filter MackySoft.SerializeReferenceExtensions.Tests.GenericValueTypeVarianceReproductionTests.Covariance_DoesNotBoxValueTypeArguments \ - --output Logs/TestResults/value-variance.xml --timeout 900 \ - -- -logFile Logs/value-variance.log -``` - -Use `Contravariance_DoesNotBoxValueTypeArguments` as the method name for the other -case. An assertion failure is the current expected reproduction result; after a -product fix, remove `Explicit` and include these exclusions in ordinary CI. From 88cd071b77fc40f5c173a1dd027fd79fc0dc8772 Mon Sep 17 00:00:00 2001 From: Makihiro <13536348+mackysoft@users.noreply.github.com> Date: Fri, 16 Oct 2026 02:59:10 +0900 Subject: [PATCH 5/5] ci(tests): limit push checks to main Why Avoid duplicate Tests workflow runs when a branch with an open pull request receives a push. What Changed Restrict the push trigger to main while retaining pull_request and workflow_dispatch triggers. Impact Pull request updates run one Linux EditMode matrix. Main pushes and manual runs remain available. --- .github/workflows/tests.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 073b7c0..2245475 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -2,9 +2,8 @@ name: Tests on: push: - branches-ignore: - - documentation - - gh-pages + branches: + - main pull_request: {} workflow_dispatch: {}