diff --git a/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTree.cs b/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTree.cs index 9d8a162..b6e1f8a 100644 --- a/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTree.cs +++ b/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTree.cs @@ -2,13 +2,13 @@ { using System.Collections.Immutable; - public interface IRule { } + interface IRule { } - public interface IProjectPropertiesContext { } + interface IProjectPropertiesContext { } - public interface IPropertySheet { } + interface IPropertySheet { } - public class ProjectPropertiesContext : IProjectPropertiesContext + class ProjectPropertiesContext : IProjectPropertiesContext { } @@ -18,12 +18,12 @@ partial class ProjectTree [Required] readonly string caption; readonly string filePath; - readonly System.Drawing.Image icon; - readonly System.Drawing.Image expandedIcon; + readonly string iconMoniker; + readonly string expandedIconMoniker; readonly bool visible; readonly IRule browseObjectProperties; readonly ImmutableHashSet capabilities; - readonly ImmutableSortedSet children; + readonly ImmutableList children; } [GenerateImmutable(DefineInterface = true, GenerateBuilder = true, DefineWithMethodsPerProperty = true, DefineRootedStruct = true, Delta = true)] diff --git a/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreeNodeTest.cs b/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreeNodeTest.cs index ce63c28..c8395f6 100644 --- a/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreeNodeTest.cs +++ b/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreeNodeTest.cs @@ -441,11 +441,17 @@ public void MovingNodeAroundHierarchy() var moved = root.RemoveDescendent(aa).AddDescendent(aa, ab); var history = moved.ChangesSince(root); - Assert.Equal(1, history.Count); + Assert.Equal(2, history.Count); + Assert.Equal(ChangeKind.Replaced, history[0].Kind); Assert.Same(aa, history[0].Before); Assert.Same(aa, history[0].After); Assert.Equal(ProjectTreeChangedProperties.Parent, history[0].Changes); + + Assert.Equal(ChangeKind.Replaced, history[1].Kind); + Assert.Same(ab, history[1].Before); + Assert.Same(moved.Children[0], history[1].After); + Assert.Equal(ProjectTreeChangedProperties.PositionUnderParent, history[1].Changes); } [Fact] @@ -460,12 +466,19 @@ public void MovingNodeAroundHierarchyWithOtherChanges() var moved = root.RemoveDescendent(aa).AddDescendent(aaModified, ab); var history = moved.ChangesSince(root); - Assert.Equal(1, history.Count); + Assert.Equal(2, history.Count); + Assert.Equal(ChangeKind.Replaced, history[0].Kind); Assert.Equal(ProjectTreeChangedProperties.Parent | ProjectTreeChangedProperties.Visible, history[0].Changes); Assert.Same(aa, history[0].Before); Assert.Same(aaModified, history[0].After); Assert.Equal(aa.Identity, history[0].Identity); + + Assert.Equal(ChangeKind.Replaced, history[1].Kind); + Assert.Equal(ProjectTreeChangedProperties.PositionUnderParent, history[1].Changes); + Assert.Same(ab, history[1].Before); + Assert.Same(moved.Children[0], history[1].After); + Assert.Equal(ab.Identity, history[1].Identity); } [Fact] @@ -480,17 +493,24 @@ public void MovingNodeAroundHierarchyWithChildAdds() var moved = root.RemoveDescendent(aa).AddDescendent(aaModified, ab); var history = moved.ChangesSince(root); - Assert.Equal(2, history.Count); + Assert.Equal(3, history.Count); + Assert.Equal(ChangeKind.Replaced, history[0].Kind); Assert.Equal(ProjectTreeChangedProperties.Parent, history[0].Changes); Assert.Same(aa, history[0].Before); Assert.Same(aaModified, history[0].After); Assert.Equal(aa.Identity, history[0].Identity); - Assert.Equal(ChangeKind.Added, history[1].Kind); - Assert.Same(aaModified.Children[0], history[1].After); - Assert.Null(history[1].Before); - Assert.Equal(aaModified.Children[0].Identity, history[1].Identity); + Assert.Equal(ChangeKind.Replaced, history[1].Kind); + Assert.Equal(ProjectTreeChangedProperties.PositionUnderParent, history[1].Changes); + Assert.Same(ab, history[1].Before); + Assert.Same(moved.Children[0], history[1].After); + Assert.Equal(ab.Identity, history[1].Identity); + + Assert.Equal(ChangeKind.Added, history[2].Kind); + Assert.Same(aaModified.Children[0], history[2].After); + Assert.Null(history[2].Before); + Assert.Equal(aaModified.Children[0].Identity, history[2].Identity); } [Fact] @@ -506,7 +526,8 @@ public void MovingNodeAroundHierarchyWithChildRemoves() var moved = root.RemoveDescendent(aa).AddDescendent(aaModified, ab); var history = moved.ChangesSince(root); - Assert.Equal(2, history.Count); + Assert.Equal(3, history.Count); + Assert.Equal(ChangeKind.Removed, history[0].Kind); Assert.Same(aa.Children[0], history[0].Before); Assert.Null(history[0].After); @@ -517,6 +538,12 @@ public void MovingNodeAroundHierarchyWithChildRemoves() Assert.Same(aa, history[1].Before); Assert.Same(aaModified, history[1].After); Assert.Equal(aa.Identity, history[1].Identity); + + Assert.Equal(ChangeKind.Replaced, history[2].Kind); + Assert.Equal(ProjectTreeChangedProperties.PositionUnderParent, history[2].Changes); + Assert.Same(ab, history[2].Before); + Assert.Same(moved.Children[0], history[2].After); + Assert.Equal(ab.Identity, history[2].Identity); } [Fact] @@ -527,7 +554,8 @@ public void RepositioningNodeWithinParentsChildren() aa = ProjectTree.Create("AA"), ab = ProjectTree.Create("AB")); var ac = aa.WithCaption("AC"); - var modified = root.ReplaceDescendent(aa, ac); + // TODO: we should generate a method for ordered collections for reordering. + var modified = root.WithChildren(root.Children.Remove(aa).Insert(1, ac)); var history = modified.ChangesSince(root); Assert.Equal(1, history.Count); @@ -536,5 +564,32 @@ public void RepositioningNodeWithinParentsChildren() Assert.Same(aa, history[0].Before); Assert.Same(ac, history[0].After); } + + [Fact(Skip = "Not yet passing")] + public void ReorderChildrenInOrderedList() + { + ProjectTree aa, ab, ac, ad, ae; + var original = ProjectTree.Create("A").WithChildren( + aa = ProjectTree.Create("AA"), + ab = ProjectTree.Create("AB"), + ac = ProjectTree.Create("AC"), + ad = ProjectTree.Create("AD"), + ae = ProjectTree.Create("AE")); + var reordered = original.RemoveChild(ac).AddChild(ac); // move AC to bottom. + Assert.Equal(new string[] { "AA", "AB", "AD", "AE", "AC" }, reordered.Children.Select(c => c.Caption)); + + var changes = reordered.ChangesSince(original); + Assert.NotEmpty(changes); + + // Now move it back to its original position. + var restored = reordered.WithChildren( + reordered.Children.Remove(ac).Insert(2, ac)); + Assert.Equal(new string[] { "AA", "AB", "AC", "AD", "AE" }, restored.Children.Select(c => c.Caption)); + + changes = restored.ChangesSince(reordered); + Assert.NotEmpty(changes); + + Assert.Empty(restored.ChangesSince(original)); + } } } diff --git a/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreeNodeTestBase.cs b/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreeNodeTestBase.cs index ccd9f47..b34322f 100644 --- a/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreeNodeTestBase.cs +++ b/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreeNodeTestBase.cs @@ -27,12 +27,12 @@ public abstract class ProjectTreeNodeTestBase : IDisposable private int nodeCounter; - internal ImmutableSortedSet Children { get; set; } + internal ImmutableList Children { get; set; } public ProjectTreeNodeTestBase() { this.nodeCounter = 0; - this.Children = ImmutableSortedSet.Create(ProjectTreeSort.Default); + this.Children = ImmutableList.Create(); } public void Dispose() diff --git a/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreePartial.cs b/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreePartial.cs index fd50af6..93dda3d 100644 --- a/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreePartial.cs +++ b/src/ImmutableObjectGraph.Generation.Tests/TestSources/ProjectTreePartial.cs @@ -19,7 +19,7 @@ public static IReadOnlyList GetDelta(ProjectTree before, ProjectTree a static partial void CreateDefaultTemplate(ref ProjectTree.Template template) { - template.Children = ImmutableSortedSet.Create(ProjectTreeSort.Default); + template.Children = ImmutableList.Create(); template.Capabilities = ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase); template.Visible = true; } diff --git a/src/ImmutableObjectGraph.Generation/CodeGen+CollectionHelpersGen.cs b/src/ImmutableObjectGraph.Generation/CodeGen+CollectionHelpersGen.cs index f711edd..8b5769b 100644 --- a/src/ImmutableObjectGraph.Generation/CodeGen+CollectionHelpersGen.cs +++ b/src/ImmutableObjectGraph.Generation/CodeGen+CollectionHelpersGen.cs @@ -208,10 +208,13 @@ private MethodDeclarationSyntax CreateParamsElementArrayMethod(MetaField field, var lambdaParameter = SyntaxFactory.Parameter(SyntaxFactory.Identifier("v")); var argument = passThroughChildSync ? (ExpressionSyntax)Syntax.EnumerableExtension( - SyntaxFactory.IdentifierName(nameof(Enumerable.Select)), - ValuesParameterName, - SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList( - SyntaxFactory.Argument(Syntax.ThisDot(SyncImmediateChildToCurrentVersionMethodName))))) + SyntaxFactory.IdentifierName(nameof(Enumerable.ToList)), + Syntax.EnumerableExtension( + SyntaxFactory.IdentifierName(nameof(Enumerable.Select)), + ValuesParameterName, + SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Argument(Syntax.ThisDot(SyncImmediateChildToCurrentVersionMethodName))))), + SyntaxFactory.ArgumentList()) : ValuesParameterName; paramsArrayMethod = this.AddMethodBody( diff --git a/src/ImmutableObjectGraph.Generation/CodeGen.cs b/src/ImmutableObjectGraph.Generation/CodeGen.cs index b9323d4..a564fc9 100644 --- a/src/ImmutableObjectGraph.Generation/CodeGen.cs +++ b/src/ImmutableObjectGraph.Generation/CodeGen.cs @@ -1289,24 +1289,9 @@ public MetaType RootAncestorOrThisType } } - public bool ChildrenAreSorted - { - get - { - // Not very precise, but it does the job for now. - return this.RecursiveParent.RecursiveField.Type.Name == nameof(ImmutableSortedSet); - } - } + public bool ChildrenAreSorted => this.RecursiveParent.RecursiveField.IsCollectionSorted; - public bool ChildrenAreOrdered - { - get - { - // Not very precise, but it does the job for now. - var namedType = this.RecursiveParent.RecursiveField.Type as INamedTypeSymbol; - return namedType != null && namedType.AllInterfaces.Any(iface => iface.Name == nameof(IReadOnlyList)); - } - } + public bool ChildrenAreOrdered => this.RecursiveParent.RecursiveField.IsCollectionOrdered; public IEnumerable GetFieldsBeyond(MetaType ancestor) { @@ -1435,6 +1420,35 @@ public MetaType TypeAsGeneratedImmutable public bool IsDictionary => IsDictionaryType(this.Symbol.Type); + /// + /// Gets a value indicating whether the elements in the collection have a defined order. + /// This may be because it is sorted, or because it is documented to retain the order + /// in which the elements were inserted (e.g. a list). + /// + public bool IsCollectionOrdered + { + get + { + if (this.IsCollection) + { + // Not very precise, but it does the job for now. + var namedType = this.Type as INamedTypeSymbol; + return namedType?.AllInterfaces.Any(iface => iface.Name == nameof(IReadOnlyList)) ?? false; + } + + return false; + } + } + + public bool IsCollectionSorted + { + get + { + // Not very precise, but it does the job for now. + return this.Type.Name == nameof(ImmutableSortedSet); + } + } + public MetaType DeclaringType { get { return new MetaType(this.metaType.Generator, this.Symbol.ContainingType); }