diff --git a/Alchemy.SourceGenerator.Tests/GeneratorUtils.cs b/Alchemy.SourceGenerator.Tests/GeneratorUtils.cs index e6c920d..98bb307 100644 --- a/Alchemy.SourceGenerator.Tests/GeneratorUtils.cs +++ b/Alchemy.SourceGenerator.Tests/GeneratorUtils.cs @@ -69,6 +69,8 @@ static ImmutableArray LoadFrameworkReferences() namespace UnityEngine { public class Object { } + public class GameObject : Object { } + public class MonoBehaviour : Object { } public interface ISerializationCallbackReceiver { void OnBeforeSerialize(); diff --git a/Alchemy.SourceGenerator.Tests/NamespaceTests.cs b/Alchemy.SourceGenerator.Tests/NamespaceTests.cs index e44e6d2..7bd3cdf 100644 --- a/Alchemy.SourceGenerator.Tests/NamespaceTests.cs +++ b/Alchemy.SourceGenerator.Tests/NamespaceTests.cs @@ -134,4 +134,59 @@ public partial class Player await Assert.That(result.AllGeneratedText).Contains("__alchemySerializationData_My_Game_Player"); } + + [Test] + public async Task Dictionary_types_are_globally_qualified_when_root_namespace_is_shadowed() + { + var result = GeneratorUtils.Run(""" + using System; + using System.Collections.Generic; + using Alchemy.Serialization; + using Artillery.Entities; + using Artillery.Entities.Units; + + namespace Artillery.Artillery + { + public sealed class NamespaceCollision { } + } + + namespace Artillery.Entities + { + public enum UnitTeam + { + Ally, + Enemy + } + } + + namespace Artillery.Entities.Units + { + public class BasicUnit : UnityEngine.MonoBehaviour { } + } + + namespace Artillery.Entities.Units.Configuration + { + [AlchemySerialize] + public partial class UnitConfiguration + { + [AlchemySerializeField, NonSerialized] + public Dictionary teamObjects = new(); + + [AlchemySerializeField, NonSerialized] + public Dictionary units = new(); + + [AlchemySerializeField, NonSerialized] + public Dictionary teamUnits = new(); + } + } + """); + + await Assert.That(result.AllGeneratedText).Contains( + "FromJson>"); + await Assert.That(result.AllGeneratedText).Contains( + "FromJson>"); + await Assert.That(result.AllGeneratedText).Contains( + "FromJson>"); + await Assert.That(result.DescribeCompilationErrors()).IsEqualTo(""); + } } diff --git a/Alchemy.SourceGenerator/AlchemySerializeGenerator.cs b/Alchemy.SourceGenerator/AlchemySerializeGenerator.cs index 4061bed..6a5dc5e 100644 --- a/Alchemy.SourceGenerator/AlchemySerializeGenerator.cs +++ b/Alchemy.SourceGenerator/AlchemySerializeGenerator.cs @@ -189,7 +189,7 @@ static string ProcessClass(INamedTypeSymbol typeSymbol, List field {{ if ({alchemySerializationDataName}.{field.Name}.isCreated) {{ - this.{field.Name} = global::Alchemy.Serialization.Internal.SerializationHelper.FromJson<{field.Type.ToDisplayString()}>({alchemySerializationDataName}.{field.Name}.data, {alchemySerializationDataName}.UnityObjectReferences); + this.{field.Name} = global::Alchemy.Serialization.Internal.SerializationHelper.FromJson<{field.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>({alchemySerializationDataName}.{field.Name}.data, {alchemySerializationDataName}.UnityObjectReferences); }} }} catch (global::System.Exception ex) diff --git a/Alchemy/Assets/Alchemy/Editor/Elements/DictionaryField.cs b/Alchemy/Assets/Alchemy/Editor/Elements/DictionaryField.cs index 4f9186f..e25fbb8 100644 --- a/Alchemy/Assets/Alchemy/Editor/Elements/DictionaryField.cs +++ b/Alchemy/Assets/Alchemy/Editor/Elements/DictionaryField.cs @@ -142,7 +142,10 @@ public override void Lock() valueField.OnValueChanged -= SetValue; valueField.OnValueChanged += x => { - ReflectionHelper.GetProperty(collection.GetType(), "Item").SetValue(collection, x, new object[] { key }); + value = x; + keyValuePair = Activator.CreateInstance(kvType, key, value); + ReflectionHelper.GetProperty(collection.GetType(), "Item").SetValue(collection, value, new object[] { key }); + OnValueChanged?.Invoke(keyValuePair); }; } diff --git a/Alchemy/Assets/Alchemy/Editor/Elements/GenericField.cs b/Alchemy/Assets/Alchemy/Editor/Elements/GenericField.cs index 09374f9..f464f0c 100644 --- a/Alchemy/Assets/Alchemy/Editor/Elements/GenericField.cs +++ b/Alchemy/Assets/Alchemy/Editor/Elements/GenericField.cs @@ -99,6 +99,22 @@ void Build(object obj, Type type, string label, bool isDelayed) { AddField(new IntegerField(label), (int)obj); } + else if (type == typeof(sbyte)) + { + AddSmallIntegerField(label, (sbyte)obj, sbyte.MinValue, sbyte.MaxValue, x => (sbyte)x); + } + else if (type == typeof(byte)) + { + AddSmallIntegerField(label, (byte)obj, byte.MinValue, byte.MaxValue, x => (byte)x); + } + else if (type == typeof(short)) + { + AddSmallIntegerField(label, (short)obj, short.MinValue, short.MaxValue, x => (short)x); + } + else if (type == typeof(ushort)) + { + AddSmallIntegerField(label, (ushort)obj, ushort.MinValue, ushort.MaxValue, x => (ushort)x); + } else if (type == typeof(uint)) { @@ -256,6 +272,34 @@ void Build(object obj, Type type, string label, bool isDelayed) bool isDelayed; bool changed; + void AddSmallIntegerField(string label, int value, int minValue, int maxValue, Func convert) + { + var control = new IntegerField(label) { value = value }; + control.RegisterValueChangedCallback(x => + { + var newValue = Math.Clamp(x.newValue, minValue, maxValue); + control.SetValueWithoutNotify(newValue); + if (isDelayed) + { + changed = true; + } + else + { + OnValueChanged?.Invoke(convert(newValue)); + } + }); + if (isDelayed) + { + control.RegisterCallback(_ => + { + if (!changed) return; + OnValueChanged?.Invoke(convert(control.value)); + changed = false; + }); + } + Add(control); + } + void AddField(BaseField control, T value) { control.value = value; diff --git a/Alchemy/Assets/Alchemy/Editor/Elements/HashMapFieldBase.cs b/Alchemy/Assets/Alchemy/Editor/Elements/HashMapFieldBase.cs index 2e8fd28..1fc02b4 100644 --- a/Alchemy/Assets/Alchemy/Editor/Elements/HashMapFieldBase.cs +++ b/Alchemy/Assets/Alchemy/Editor/Elements/HashMapFieldBase.cs @@ -118,6 +118,7 @@ public void Rebuild() foreach (var item in (IEnumerable)collection) { var element = CreateItem(collection, item, "Element " + i); + element.OnValueChanged += _ => OnValueChanged?.Invoke(collection); element.OnClose += () => { if (isInputting) return; diff --git a/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll b/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll index 1390888..4480e17 100644 Binary files a/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll and b/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll differ diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/DictionarySerializationTest.prefab b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/DictionarySerializationTest.prefab new file mode 100644 index 0000000..9152ffc --- /dev/null +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/DictionarySerializationTest.prefab @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1515897830369383587 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8483165521676513262} + - component: {fileID: 8927840334069668456} + m_Layer: 0 + m_Name: DictionarySerializationTest + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8483165521676513262 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1515897830369383587} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8927840334069668456 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1515897830369383587} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 49945db4d049472db986e6e1eddce306, type: 3} + m_Name: + m_EditorClassIdentifier: Alchemy.Tests.EditorUI::Alchemy.Tests.EditorUI.DictionarySerializationTest + __alchemySerializationData_Alchemy_Tests_EditorUI_DictionarySerializationTest: + sbyteKeys: + isCreated: 1 + data: "[\n {\n \"Key\": -101,\n \"Value\": 1\n }\n]" + byteKeys: + isCreated: 1 + data: "[\n {\n \"Key\": 251,\n \"Value\": 2\n }\n]" + shortKeys: + isCreated: 1 + data: "[\n {\n \"Key\": -30001,\n \"Value\": 3\n }\n]" + ushortKeys: + isCreated: 1 + data: "[\n {\n \"Key\": 60001,\n \"Value\": 4\n }\n]" + intKeys: + isCreated: 1 + data: "[\n {\n \"Key\": -2000000001,\n \"Value\": 5\n }\n]" + uintKeys: + isCreated: 1 + data: "[\n {\n \"Key\": 4000000001,\n \"Value\": 6\n }\n]" + longKeys: + isCreated: 1 + data: "[\n {\n \"Key\": -900000000000000001,\n \"Value\": + 7\n }\n]" + ulongKeys: + isCreated: 1 + data: "[\n {\n \"Key\": 18000000000000000001,\n \"Value\": + 8\n }\n]" + floatKeys: + isCreated: 1 + data: "[\n {\n \"Key\": 123.625,\n \"Value\": 9\n }\n]" + doubleKeys: + isCreated: 1 + data: "[\n {\n \"Key\": -98765.5,\n \"Value\": 10\n }\n]" + boolKeys: + isCreated: 1 + data: "[\n {\n \"Key\": true,\n \"Value\": 11\n }\n]" + stringKeys: + isCreated: 1 + data: "{\n \"Alchemy\": 12\n}" + unityObjectReferences: [] diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/DictionarySerializationTest.prefab.meta b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/DictionarySerializationTest.prefab.meta new file mode 100644 index 0000000..1a75f10 --- /dev/null +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/DictionarySerializationTest.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ad85fb841e5e7e74e9ab924ec39804cf +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Documentation/DictionarySerializationTest.cs b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Documentation/DictionarySerializationTest.cs new file mode 100644 index 0000000..c22167a --- /dev/null +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Documentation/DictionarySerializationTest.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using Alchemy.Inspector; +using UnityEngine; +#if ALCHEMY_SUPPORT_SERIALIZATION +using Alchemy.Serialization; +#endif + +namespace Alchemy.Tests.EditorUI +{ +#if ALCHEMY_SUPPORT_SERIALIZATION + [AlchemySerialize] +#endif + [DocumentationSample] + public partial class DictionarySerializationTest : MonoBehaviour + { +#if ALCHEMY_SUPPORT_SERIALIZATION + [HorizontalGroup("8-bit")] + [AlchemySerializeField, NonSerialized] + public Dictionary sbyteKeys = new() { [(sbyte)-101] = 1 }; + + [HorizontalGroup("8-bit")] + [AlchemySerializeField, NonSerialized] + public Dictionary byteKeys = new() { [(byte)251] = 2 }; + + [HorizontalGroup("16-bit")] + [AlchemySerializeField, NonSerialized] + public Dictionary shortKeys = new() { [(short)-30001] = 3 }; + + [HorizontalGroup("16-bit")] + [AlchemySerializeField, NonSerialized] + public Dictionary ushortKeys = new() { [(ushort)60001] = 4 }; + + [HorizontalGroup("32-bit")] + [AlchemySerializeField, NonSerialized] + public Dictionary intKeys = new() { [-2000000001] = 5 }; + + [HorizontalGroup("32-bit")] + [AlchemySerializeField, NonSerialized] + public Dictionary uintKeys = new() { [4000000001u] = 6 }; + + [HorizontalGroup("64-bit")] + [AlchemySerializeField, NonSerialized] + public Dictionary longKeys = new() { [-900000000000000001L] = 7 }; + + [HorizontalGroup("64-bit")] + [AlchemySerializeField, NonSerialized] + public Dictionary ulongKeys = new() { [18000000000000000001UL] = 8 }; + + [HorizontalGroup("Floating Point")] + [AlchemySerializeField, NonSerialized] + public Dictionary floatKeys = new() { [123.625f] = 9 }; + + [HorizontalGroup("Floating Point")] + [AlchemySerializeField, NonSerialized] + public Dictionary doubleKeys = new() { [-98765.5d] = 10 }; + + [HorizontalGroup("Other")] + [AlchemySerializeField, NonSerialized] + public Dictionary boolKeys = new() { [true] = 11 }; + + [HorizontalGroup("Other")] + [AlchemySerializeField, NonSerialized] + public Dictionary stringKeys = new() { ["Alchemy"] = 12 }; +#endif + } +} diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Documentation/DictionarySerializationTest.cs.meta b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Documentation/DictionarySerializationTest.cs.meta new file mode 100644 index 0000000..8fb2170 --- /dev/null +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Documentation/DictionarySerializationTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49945db4d049472db986e6e1eddce306 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor.meta b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode.meta similarity index 100% rename from tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor.meta rename to tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode.meta diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/Alchemy.Tests.EditorUI.Editor.asmdef b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/Alchemy.Tests.EditorUI.EditMode.asmdef similarity index 88% rename from tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/Alchemy.Tests.EditorUI.Editor.asmdef rename to tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/Alchemy.Tests.EditorUI.EditMode.asmdef index e79bc2a..b04e1d1 100644 --- a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/Alchemy.Tests.EditorUI.Editor.asmdef +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/Alchemy.Tests.EditorUI.EditMode.asmdef @@ -1,6 +1,6 @@ { - "name": "Alchemy.Tests.EditorUI.Editor", - "rootNamespace": "Alchemy.Tests.EditorUI.Editor", + "name": "Alchemy.Tests.EditorUI.EditMode", + "rootNamespace": "Alchemy.Tests.EditorUI.EditMode", "references": [ "GUID:27619889b8ba8c24980f49ee34dbb44a", "GUID:0acc523941302664db1f4e527237feb3", diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/Alchemy.Tests.EditorUI.Editor.asmdef.meta b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/Alchemy.Tests.EditorUI.EditMode.asmdef.meta similarity index 100% rename from tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/Alchemy.Tests.EditorUI.Editor.asmdef.meta rename to tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/Alchemy.Tests.EditorUI.EditMode.asmdef.meta diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/DocumentationEditorSamples.cs b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/DocumentationEditorSamples.cs similarity index 99% rename from tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/DocumentationEditorSamples.cs rename to tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/DocumentationEditorSamples.cs index f16ea5f..1755cbb 100644 --- a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/DocumentationEditorSamples.cs +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/DocumentationEditorSamples.cs @@ -7,7 +7,7 @@ using UnityEngine; using UnityEngine.UIElements; -namespace Alchemy.Tests.EditorUI.Editor +namespace Alchemy.Tests.EditorUI.EditMode { public sealed class DocumentationEditorWindow : AlchemyEditorWindow { diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/DocumentationEditorSamples.cs.meta b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/DocumentationEditorSamples.cs.meta similarity index 100% rename from tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/DocumentationEditorSamples.cs.meta rename to tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/DocumentationEditorSamples.cs.meta diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/DocumentationEditorSamplesTest.cs b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/DocumentationEditorSamplesTest.cs similarity index 99% rename from tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/DocumentationEditorSamplesTest.cs rename to tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/DocumentationEditorSamplesTest.cs index 3a53987..f49ceed 100644 --- a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/DocumentationEditorSamplesTest.cs +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/DocumentationEditorSamplesTest.cs @@ -8,7 +8,7 @@ using UnityEngine; using UnityEngine.UIElements; -namespace Alchemy.Tests.EditorUI.Editor +namespace Alchemy.Tests.EditorUI.EditMode { public class DocumentationEditorSamplesTest { diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/DocumentationEditorSamplesTest.cs.meta b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/DocumentationEditorSamplesTest.cs.meta similarity index 100% rename from tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/Editor/DocumentationEditorSamplesTest.cs.meta rename to tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/EditMode/DocumentationEditorSamplesTest.cs.meta diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor.meta b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor.meta new file mode 100644 index 0000000..d86b8b4 --- /dev/null +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dcba72005a6ab4576baeb3d4b0dedb08 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/Alchemy.Tests.EditorUI.PlayModeInEditor.asmdef b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/Alchemy.Tests.EditorUI.PlayModeInEditor.asmdef new file mode 100644 index 0000000..751e63e --- /dev/null +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/Alchemy.Tests.EditorUI.PlayModeInEditor.asmdef @@ -0,0 +1,30 @@ +{ + "name": "Alchemy.Tests.EditorUI.PlayModeInEditor", + "rootNamespace": "Alchemy.Tests.EditorUI.PlayModeInEditor", + "references": [ + "GUID:27619889b8ba8c24980f49ee34dbb44a", + "GUID:0acc523941302664db1f4e527237feb3", + "GUID:88be65f96b86746888c927a5c8ff3534", + "GUID:9b64a2fc1d4b46d2bb1c83d6bc7287ff", + "GUID:5fd453cd0d182422093c4a764fd5eadb" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [ + { + "name": "com.unity.serialization", + "expression": "", + "define": "ALCHEMY_SUPPORT_SERIALIZATION" + } + ], + "noEngineReferences": false +} diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/Alchemy.Tests.EditorUI.PlayModeInEditor.asmdef.meta b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/Alchemy.Tests.EditorUI.PlayModeInEditor.asmdef.meta new file mode 100644 index 0000000..43b5fb3 --- /dev/null +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/Alchemy.Tests.EditorUI.PlayModeInEditor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6c628359950e490aa85d4071be84cf06 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/DictionaryFieldSerializationTest.cs b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/DictionaryFieldSerializationTest.cs new file mode 100644 index 0000000..c895f10 --- /dev/null +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/DictionaryFieldSerializationTest.cs @@ -0,0 +1,87 @@ +#if ALCHEMY_SUPPORT_SERIALIZATION +using System; +using System.Collections; +using System.Collections.Generic; +using Alchemy.Editor.Elements; +using Alchemy.Serialization; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using UnityEngine.UIElements; + +namespace Alchemy.Tests.EditorUI.PlayModeInEditor +{ + [AlchemySerialize] + internal partial class DictionaryCollectionSerializationTarget + { + [AlchemySerializeField, NonSerialized] + public Dictionary arrayValues = new(); + + [AlchemySerializeField, NonSerialized] + public Dictionary> listValues = new(); + } + + public class DictionaryFieldSerializationTest + { + [UnityTest] + public IEnumerator Test_ArrayValueEditSurvivesSerializationRoundTrip() + { + return Test_NestedCollectionEditSurvivesSerializationRoundTrip( + nameof(DictionaryCollectionSerializationTarget.arrayValues)); + } + + [UnityTest] + public IEnumerator Test_ListValueEditSurvivesSerializationRoundTrip() + { + return Test_NestedCollectionEditSurvivesSerializationRoundTrip( + nameof(DictionaryCollectionSerializationTarget.listValues)); + } + + static IEnumerator Test_NestedCollectionEditSurvivesSerializationRoundTrip(string fieldName) + { + var target = new DictionaryCollectionSerializationTarget + { + arrayValues = new Dictionary { [1] = new[] { "before" } }, + listValues = new Dictionary> { [1] = new() { "before" } }, + }; + var callback = (ISerializationCallbackReceiver)target; + callback.OnBeforeSerialize(); + + var fieldInfo = typeof(DictionaryCollectionSerializationTarget).GetField(fieldName); + Assert.That(fieldInfo, Is.Not.Null); + + var reflectionField = new ReflectionField(target, fieldInfo); + var foldout = EditorTestUtility.QueryRequired(reflectionField); + foldout.value = true; + var window = EditorTestUtility.ShowInWindow(reflectionField); + try + { + yield return null; + + var dictionaryItem = EditorTestUtility.QueryRequired(reflectionField); + var listField = EditorTestUtility.QueryRequired(dictionaryItem); + var listView = EditorTestUtility.QueryRequired(listField); + listView.Rebuild(); + yield return null; + + var textField = EditorTestUtility.QueryRequired( + listView, + field => field.label == "Element 0"); + textField.value = "after"; + + var dictionary = (IDictionary)fieldInfo.GetValue(target); + Assert.That(((IList)dictionary[1])[0], Is.EqualTo("after")); + + callback.OnAfterDeserialize(); + + dictionary = (IDictionary)fieldInfo.GetValue(target); + Assert.That(((IList)dictionary[1])[0], Is.EqualTo("after")); + } + finally + { + UnityEngine.Object.DestroyImmediate(window); + } + } + } +} +#endif diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/DictionaryFieldSerializationTest.cs.meta b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/DictionaryFieldSerializationTest.cs.meta new file mode 100644 index 0000000..a4132b4 --- /dev/null +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/DictionaryFieldSerializationTest.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 047849e0939a4f1e9957d4cc8030ca24 diff --git a/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/DictionaryFieldTest.cs b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/DictionaryFieldTest.cs new file mode 100644 index 0000000..b75b2c2 --- /dev/null +++ b/tests/Alchemy.Tests/Assets/Alchemy.Tests.EditorUI/PlayModeInEditor/DictionaryFieldTest.cs @@ -0,0 +1,154 @@ +using System.Collections; +using System.Collections.Generic; +using Alchemy.Editor.Elements; +using NUnit.Framework; +using UnityEngine.TestTools; +using UnityEngine.UIElements; + +namespace Alchemy.Tests.EditorUI.PlayModeInEditor +{ + public class DictionaryFieldTest + { + sealed class SmallIntegerFieldTarget + { + public SmallIntegerFieldTarget(T value) + { + this.value = value; + } + + public T value; + } + + sealed class SmallIntegerCase + { + public SmallIntegerCase(IDictionary dictionary, object key) + { + Dictionary = dictionary; + Key = key; + } + + public IDictionary Dictionary { get; } + public object Key { get; } + } + + sealed class SmallIntegerFieldCase + { + public SmallIntegerFieldCase(object target, int input, object initialValue, object expectedValue) + { + Target = target; + Input = input; + InitialValue = initialValue; + ExpectedValue = expectedValue; + } + + public object Target { get; } + public int Input { get; } + public object InitialValue { get; } + public object ExpectedValue { get; } + } + + [UnityTest] + public IEnumerator Test_SmallIntegerKeyInputCanBeEdited() + { + var cases = new[] + { + new SmallIntegerCase(new Dictionary(), (sbyte)-7), + new SmallIntegerCase(new Dictionary(), (byte)7), + new SmallIntegerCase(new Dictionary(), (short)-300), + new SmallIntegerCase(new Dictionary(), (ushort)600), + }; + + foreach (var testCase in cases) + { + var field = new DictionaryField(testCase.Dictionary, "Dictionary"); + var window = EditorTestUtility.ShowInWindow(field); + try + { + yield return null; + + var addButton = EditorTestUtility.QueryRequired