Skip to content

Commit c6e2274

Browse files
committed
fix
1 parent f605055 commit c6e2274

4 files changed

Lines changed: 286 additions & 2 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
using System.Collections.Generic;
2+
using Eocron.Algorithms.UI.Editing;
3+
using FluentAssertions;
4+
using NUnit.Framework;
5+
6+
namespace Eocron.Algorithms.Tests;
7+
8+
[TestFixture]
9+
public class EditSessionTests
10+
{
11+
private TestDocument _document;
12+
13+
[Test]
14+
public void Check_DeepSetAndUndo()
15+
{
16+
var session = new EditSession<TestDocument>(_document);
17+
session.BeginEdit();
18+
session.CanRedo.Should().BeFalse();
19+
session.CanUndo.Should().BeFalse();
20+
21+
session.SetProperty(x=> x.Inner.Inner.Id, "3");
22+
23+
session.Draft.Should().BeEquivalentTo(new TestDocument
24+
{
25+
Id = "1",
26+
Inner = new TestDocument
27+
{
28+
Id = "2",
29+
Inner = new TestDocument()
30+
{
31+
Id = "3"
32+
}
33+
}
34+
});
35+
36+
session.Undo();
37+
session.Draft.Should().BeEquivalentTo(new TestDocument
38+
{
39+
Id = "1",
40+
Inner = new TestDocument
41+
{
42+
Id = "2"
43+
}
44+
});
45+
}
46+
47+
[Test]
48+
public void Check_SetAndUndoAndRedo()
49+
{
50+
var session = new EditSession<TestDocument>(_document);
51+
session.BeginEdit();
52+
session.SetProperty(x=> x.Id, "3");
53+
54+
session.CanRedo.Should().BeFalse();
55+
session.CanUndo.Should().BeTrue();
56+
57+
session.Draft.Should().BeEquivalentTo(new TestDocument
58+
{
59+
Id = "3",
60+
Inner = new TestDocument
61+
{
62+
Id = "2"
63+
}
64+
});
65+
66+
session.Undo();
67+
session.CanRedo.Should().BeTrue();
68+
session.CanUndo.Should().BeFalse();
69+
session.Draft.Should().BeEquivalentTo(new TestDocument
70+
{
71+
Id = "1",
72+
Inner = new TestDocument
73+
{
74+
Id = "2"
75+
}
76+
});
77+
78+
session.Redo();
79+
80+
session.CanRedo.Should().BeFalse();
81+
session.CanUndo.Should().BeTrue();
82+
session.Draft.Should().BeEquivalentTo(new TestDocument
83+
{
84+
Id = "3",
85+
Inner = new TestDocument
86+
{
87+
Id = "2"
88+
}
89+
});
90+
}
91+
92+
93+
[Test]
94+
public void Check_SetAndUndoAndSet()
95+
{
96+
var session = new EditSession<TestDocument>(_document);
97+
session.BeginEdit();
98+
session.SetProperty(x=> x.Id, "3");
99+
100+
session.CanRedo.Should().BeFalse();
101+
session.CanUndo.Should().BeTrue();
102+
103+
session.Draft.Should().BeEquivalentTo(new TestDocument
104+
{
105+
Id = "3",
106+
Inner = new TestDocument
107+
{
108+
Id = "2"
109+
}
110+
});
111+
112+
session.Undo();
113+
session.CanRedo.Should().BeTrue();
114+
session.CanUndo.Should().BeFalse();
115+
session.Draft.Should().BeEquivalentTo(new TestDocument
116+
{
117+
Id = "1",
118+
Inner = new TestDocument
119+
{
120+
Id = "2"
121+
}
122+
});
123+
124+
session.SetProperty(x=> x.Id, "4");
125+
126+
session.CanRedo.Should().BeFalse();
127+
session.CanUndo.Should().BeTrue();
128+
session.Draft.Should().BeEquivalentTo(new TestDocument
129+
{
130+
Id = "4",
131+
Inner = new TestDocument
132+
{
133+
Id = "2"
134+
}
135+
});
136+
}
137+
138+
[Test]
139+
public void Check_InitialState()
140+
{
141+
var session = new EditSession<TestDocument>(_document);
142+
session.BeginEdit();
143+
session.CanRedo.Should().BeFalse();
144+
session.CanUndo.Should().BeFalse();
145+
}
146+
147+
[SetUp]
148+
public void Setup()
149+
{
150+
_document = new TestDocument
151+
{
152+
Id = "1",
153+
Inner = new TestDocument
154+
{
155+
Id = "2",
156+
}
157+
};
158+
}
159+
160+
public class TestDocument
161+
{
162+
public string Id { get; set; }
163+
164+
public TestDocument Inner { get; set; }
165+
166+
public List<TestDocument> Chidlren { get; set; }
167+
}
168+
}

Eocron.Algorithms/UI/Editing/EditSession.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace Eocron.Algorithms.UI.Editing;
77

8-
public class EditSession<TDocument> : IEditSession<TDocument>
8+
public class EditSession<TDocument> : IEditSession<TDocument> where TDocument : class
99
{
1010
public TDocument Source { get; private set; }
1111
public TDocument Draft { get; private set; }
@@ -17,7 +17,7 @@ public class EditSession<TDocument> : IEditSession<TDocument>
1717
public EditSession(TDocument source, int changeLimit = 100)
1818
{
1919
_changeLimit = changeLimit;
20-
Source = source!;
20+
Source = EditSessionHelper.DeepClone(source! ?? throw new ArgumentNullException(nameof(source)));
2121
}
2222

2323
public void BeginEdit()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
4+
namespace Eocron.Algorithms.UI.Editing
5+
{
6+
public static class EditSessionExtensions
7+
{
8+
public static void SetProperty<TDocument, TProperty>(this IEditSession<TDocument> editSession,
9+
Expression<Func<TDocument, TProperty>> propertySelector, TProperty newValue)
10+
{
11+
editSession.Apply(new PropertyEditSessionChange<TDocument, TProperty>(
12+
propertySelector,
13+
newValue));
14+
}
15+
}
16+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq.Expressions;
4+
using System.Reflection;
5+
6+
namespace Eocron.Algorithms.UI.Editing
7+
{
8+
public class PropertyEditSessionChange<TDocument, TProperty> : IEditSessionChange<TDocument>
9+
{
10+
private readonly Expression<Func<TDocument, TProperty>> _propertySelector;
11+
private readonly TProperty _newValue;
12+
private TProperty _oldValue;
13+
private readonly List<(object parent, PropertyInfo property)> _createdObjects = new();
14+
15+
public PropertyEditSessionChange(
16+
Expression<Func<TDocument, TProperty>> propertySelector,
17+
TProperty newValue)
18+
{
19+
_propertySelector = propertySelector ?? throw new ArgumentNullException(nameof(propertySelector));
20+
_newValue = newValue;
21+
}
22+
23+
public void Redo(TDocument document)
24+
{
25+
if (document == null) throw new ArgumentNullException(nameof(document));
26+
27+
var properties = GetPropertyChain(_propertySelector);
28+
29+
object current = document;
30+
31+
for (var i = 0; i < properties.Count - 1; i++)
32+
{
33+
var prop = properties[i];
34+
var value = prop.GetValue(current);
35+
36+
if (value == null)
37+
{
38+
value = Activator.CreateInstance(prop.PropertyType)
39+
?? throw new InvalidOperationException(
40+
$"Cannot create instance of {prop.PropertyType.FullName}");
41+
42+
prop.SetValue(current, value);
43+
_createdObjects.Add((current, prop));
44+
}
45+
46+
current = value;
47+
}
48+
49+
var targetProperty = properties[^1];
50+
_oldValue = (TProperty)targetProperty.GetValue(current);
51+
52+
targetProperty.SetValue(current, _newValue);
53+
}
54+
55+
public void Undo(TDocument document)
56+
{
57+
var properties = GetPropertyChain(_propertySelector);
58+
59+
object current = document;
60+
61+
for (int i = 0; i < properties.Count - 1; i++)
62+
{
63+
current = properties[i].GetValue(current);
64+
if (current == null)
65+
return;
66+
}
67+
68+
properties[^1].SetValue(current, _oldValue);
69+
70+
for (var i = _createdObjects.Count - 1; i >= 0; i--)
71+
{
72+
var (parent, property) = _createdObjects[i];
73+
property.SetValue(parent, null);
74+
}
75+
_createdObjects.Clear();
76+
}
77+
78+
private static List<PropertyInfo> GetPropertyChain(
79+
Expression<Func<TDocument, TProperty>> expression)
80+
{
81+
var properties = new List<PropertyInfo>();
82+
Expression current = expression.Body;
83+
84+
while (current is MemberExpression member)
85+
{
86+
if (member.Member is PropertyInfo property)
87+
properties.Insert(0, property);
88+
else
89+
throw new InvalidOperationException("Expression contains non-property member.");
90+
91+
current = member.Expression;
92+
}
93+
94+
if (current.NodeType != ExpressionType.Parameter)
95+
throw new InvalidOperationException("Invalid property selector expression.");
96+
97+
return properties;
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)