diff --git a/src/Artemis.Core.Tests/DataModels/DataModelPathTests.cs b/src/Artemis.Core.Tests/DataModels/DataModelPathTests.cs index 44c7eb910..6eb3e1709 100644 --- a/src/Artemis.Core.Tests/DataModels/DataModelPathTests.cs +++ b/src/Artemis.Core.Tests/DataModels/DataModelPathTests.cs @@ -340,7 +340,7 @@ public class DataModelPathTests : IDisposable } [Fact] - public void RemovingTheDynamicChild_LeavesThePathValid() + public void RemovingTheDynamicChild_MakesThePathInvalid() { DataModel.AddDynamicChild("Extra", 5); using DataModelPath path = new(DataModel, "Extra"); @@ -349,25 +349,34 @@ public class DataModelPathTests : IDisposable DataModel.RemoveDynamicChildByKey("Extra"); - // Pinned as-is: the segment only invalidates the path when the value of the removed child is the data model the - // segment looks in, rather than when the child it points to is removed. So the path stays valid and reads the - // default value instead of becoming invalid. - path.IsValid.ShouldBeTrue(); - path.GetValue().ShouldBe(0); - invalidated.ShouldBe(0); + path.IsValid.ShouldBeFalse(); + path.GetValue().ShouldBeNull(); + invalidated.ShouldBe(1); } [Fact] - public void RemovingADynamicDataModel_LeavesThePathThroughItValid() + public void RemovingTheDynamicChild_AndAddingItAgain_MakesThePathValidAgain() + { + DataModel.AddDynamicChild("Extra", 5); + using DataModelPath path = new(DataModel, "Extra"); + DataModel.RemoveDynamicChildByKey("Extra"); + + DataModel.AddDynamicChild("Extra", 6); + + path.IsValid.ShouldBeTrue(); + path.GetValue().ShouldBe(6); + } + + [Fact] + public void RemovingADynamicDataModel_MakesThePathThroughItInvalid() { DataModel.AddDynamicChild("Extra", new TestChildDataModel {Integer = 4}, "Extra data"); using DataModelPath path = new(DataModel, "Extra.Integer"); DataModel.RemoveDynamicChildByKey("Extra"); - // Pinned as-is, see RemovingTheDynamicChild_LeavesThePathValid - path.IsValid.ShouldBeTrue(); - path.GetValue().ShouldBe(0); + path.IsValid.ShouldBeFalse(); + path.GetValue().ShouldBeNull(); } [Fact] @@ -450,20 +459,17 @@ public class DataModelPathTests : IDisposable } [Fact] - public void NewPath_DoesNotFollowTheStoreUntilItIsSaved() + public void NewPath_FollowsTheStoreWithoutBeingSaved() { using DataModelPath path = new(DataModel, "Integer"); - // Pinned as-is: the constructors call Save before the path is set up, while it is still invalid, so nothing is - // saved and the entity has no data model ID. The path only reacts to its data model leaving and returning to the - // store once Save is called. Looks like Save should be called after Initialize. - path.Entity.DataModelId.ShouldBeNull(); - path.Entity.Path.ShouldBe(""); + path.Entity.DataModelId.ShouldBe(_module.Id); + path.Entity.Path.ShouldBe("Integer"); RemoveFromStore(); - path.IsValid.ShouldBeTrue(); - path.Target.ShouldBeSameAs(DataModel); + path.IsValid.ShouldBeFalse(); + path.Target.ShouldBeNull(); } [Fact] @@ -540,7 +546,7 @@ public class DataModelPathTests : IDisposable } [Fact] - public void InvalidPath_CannotFallBackToATypeOutsideTheCoreLibraries() + public void InvalidPath_FallsBackToATypeOutsideTheCoreLibraries() { using DataModelPath valid = new(DataModel, "Enum"); valid.Save(); @@ -548,9 +554,15 @@ public class DataModelPathTests : IDisposable using DataModelPath path = new(new DataModelPathEntity {Path = "Enum", DataModelId = "Missing-12345678", Type = valid.Entity.Type}); - // Pinned as-is: the type is saved by its full name without the assembly, which Type.GetType can only find in - // Artemis.Core and the .NET libraries, so types from plugins (and SkiaSharp, like SKColor) can't be found again. - path.GetPropertyType().ShouldBeNull(); + path.GetPropertyType().ShouldBe(typeof(TestEnum)); + } + + [Fact] + public void InvalidPath_FallsBackToASkiaSharpType() + { + using DataModelPath path = new(new DataModelPathEntity {Path = "Color", DataModelId = "Missing-12345678", Type = typeof(SKColor).FullName}); + + path.GetPropertyType().ShouldBe(typeof(SKColor)); } [Fact] diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs index 62ebd93bb..9039e6792 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs @@ -29,8 +29,8 @@ public class DataModelPath : IStorageModel, IDisposable, IPluginFeatureDependent _segments = []; - Save(); Initialize(); + Save(); SubscribeToDataModelStore(); } @@ -47,8 +47,8 @@ public class DataModelPath : IStorageModel, IDisposable, IPluginFeatureDependent _segments = []; - Save(); Initialize(); + Save(); SubscribeToDataModelStore(); } @@ -67,8 +67,8 @@ public class DataModelPath : IStorageModel, IDisposable, IPluginFeatureDependent _segments = []; - Save(); Initialize(); + Save(); SubscribeToDataModelStore(); } @@ -165,11 +165,20 @@ public class DataModelPath : IStorageModel, IDisposable, IPluginFeatureDependent // Fall back to stored type if (!string.IsNullOrWhiteSpace(Entity.Type)) - return Type.GetType(Entity.Type); + return FindType(Entity.Type); return null; } + private static Type? FindType(string fullName) + { + // The type is stored without its assembly, Type.GetType only finds it in Artemis.Core and the .NET libraries. Types + // of plugins and other libraries (like SKColor) are found by looking through the loaded assemblies + return Type.GetType(fullName) ?? AppDomain.CurrentDomain.GetAssemblies() + .Select(assembly => assembly.GetType(fullName)) + .FirstOrDefault(type => type != null); + } + /// /// Gets the property description of the property this path points to /// diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs index f1c71bd34..c1db971bc 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs @@ -286,8 +286,16 @@ public class DataModelPathSegment : IDisposable private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e) { - if (e.DynamicChild.BaseValue == _dynamicDataModel) + // Like when a child is added, initialize the path again so it no longer points to the removed child + if (e.Key == Identifier) + { DataModelPath.Invalidate(); + DataModelPath.Initialize(); + } + else if (e.DynamicChild.BaseValue == _dynamicDataModel) + { + DataModelPath.Invalidate(); + } } ///