Skip to content

Commit

Permalink
finished mergeability of IUnDo (fix #3)
Browse files Browse the repository at this point in the history
  • Loading branch information
Doraku committed Aug 15, 2020
1 parent bbde8aa commit a6950c1
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 62 deletions.
91 changes: 90 additions & 1 deletion source/DefaultUnDo.Test/GroupUnDoTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ public void GroupUnDo_Should_throw_ArgumentNullException_When_commands_is_null()
.WithProperty("ParamName", "commands");
}

[Fact]
public void GroupUnDo_Should_throw_ArgumentNullException_When_commands_empty()
{
Check
.ThatCode(() => new GroupUnDo())
.Throws<ArgumentException>()
.WithProperty("ParamName", "commands");
}

[Fact]
public void GroupUnDo_Should_throw_ArgumentNullException_When_commands_contains_null()
{
Expand Down Expand Up @@ -71,11 +80,91 @@ public void Undo_Should_execute_children_Undo_in_reverse()
[Fact]
public void Description_Should_return_description()
{
IUnDo undo = new GroupUnDo("test");
IUnDo undo = new GroupUnDo("test", Substitute.For<IUnDo>());

Check.That(undo.Description).IsEqualTo("test");
}

[Fact]
public void TryGetSingle_Should_return_false_When_multile_children()
{
GroupUnDo group = new GroupUnDo(
Substitute.For<IUnDo>(),
Substitute.For<IUnDo>());

Check.That(group.TryGetSingle<IUnDo>(out _)).IsFalse();
}

[Fact]
public void TryGetSingle_Should_return_false_When_single_child_is_not_T()
{
GroupUnDo group = new GroupUnDo(Substitute.For<IUnDo>());

Check.That(group.TryGetSingle<IMergeableUnDo>(out _)).IsFalse();
}

[Fact]
public void TryGetSingle_Should_return_true_When_single_child_is_T()
{
IUnDo undo = Substitute.For<IUnDo>();
GroupUnDo group = new GroupUnDo(undo);

Check.That(group.TryGetSingle(out IUnDo found)).IsTrue();
Check.That(found).IsEqualTo(undo);
}

[Fact]
public void TryMerge_Should_return_false_When_description_are_different()
{
IMergeableUnDo group = new GroupUnDo("kikoo", Substitute.For<IUnDo>());
IUnDo undo = Substitute.For<IUnDo>();
undo.Description.Returns("lol");

Check.That(group.TryMerge(undo, out _)).IsFalse();
}

[Fact]
public void TryMerge_Should_return_false_When_group_is_not_single_IMergeableUnDo()
{
IMergeableUnDo group = new GroupUnDo("test", Substitute.For<IUnDo>());
IUnDo undo = Substitute.For<IUnDo>();
undo.Description.Returns("test");

Check.That(group.TryMerge(undo, out _)).IsFalse();
}

[Fact]
public void TryMerge_Should_return_false_When_single_IMergeableUnDo_do_not_merge()
{
IUnDo undo = Substitute.For<IUnDo>();
IMergeableUnDo group = new GroupUnDo("test", Substitute.For<IMergeableUnDo>());
undo.Description.Returns("test");

Check.That(group.TryMerge(undo, out _)).IsFalse();
}

[Fact]
public void TryMerge_Should_return_true_When_merged()
{
IMergeableUnDo mergeable = Substitute.For<IMergeableUnDo>();
IUnDo undo = Substitute.For<IUnDo>();
IMergeableUnDo group = new GroupUnDo("test", mergeable);
undo.Description.Returns("test");
mergeable.TryMerge(undo, out Arg.Any<IUnDo>()).Returns(x =>
{
x[1] = undo;
return true;
});

Check.That(group.TryMerge(undo, out IUnDo merged)).IsTrue();
Check.That(merged.Description).IsEqualTo(group.Description);

IUnDo newUndo = null;
(merged as GroupUnDo)?.TryGetSingle(out newUndo);

Check.That(newUndo).IsEqualTo(undo);
}

#endregion
}
}
21 changes: 21 additions & 0 deletions source/DefaultUnDo.Test/UnDoManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,27 @@ public void Do_Should_Do(IUnDoManager manager)
Check.That(done).IsTrue();
}

[Theory]
[MemberData(nameof(UnDoManagers))]
public void Do_Should_merge_When_possible(IUnDoManager manager)
{
IMergeableUnDo mergeable = Substitute.For<IMergeableUnDo>();
IUnDo undo = Substitute.For<IUnDo>();
IUnDo merged = Substitute.For<IUnDo>();
merged.Description.Returns("yay");

mergeable.TryMerge(undo, out Arg.Any<IUnDo>()).Returns(x =>
{
x[1] = merged;
return true;
});

manager.Do(mergeable);
manager.Do(undo);

Check.That(manager.UndoDescriptions).ContainsExactly("yay");
}

[Theory]
[MemberData(nameof(UnDoManagers))]
public void Do_Should_not_add_command_in_history_when_a_group_is_going_on(IUnDoManager manager)
Expand Down
92 changes: 92 additions & 0 deletions source/DefaultUnDo.Test/ValueUnDoTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Threading;
using NFluent;
using NSubstitute;
using Xunit;

namespace DefaultUnDo.Test
Expand Down Expand Up @@ -51,6 +53,96 @@ public void Description_Should_return_description()
Check.That(undo.Description).IsEqualTo("test");
}

[Fact]
public void TryMerge_Should_return_false_When_description_are_different()
{
IMergeableUnDo value = new ValueUnDo<int>("kikoo", _ => { }, 0, 0);
IUnDo undo = Substitute.For<IUnDo>();
undo.Description.Returns("lol");

Check.That(value.TryMerge(undo, out _)).IsFalse();
}

[Fact]
public void TryMerge_Should_return_false_When_other_is_not_ValueUnDo()
{
IMergeableUnDo value = new ValueUnDo<int>("test", _ => { }, 0, 0);
IUnDo undo = Substitute.For<IUnDo>();
undo.Description.Returns("test");

Check.That(value.TryMerge(undo, out _)).IsFalse();
}

[Fact]
public void TryMerge_Should_return_false_When_other_is_not_GroupUnDo_with_single_ValueUnDo()
{
IMergeableUnDo value = new ValueUnDo<int>("test", _ => { }, 0, 0);
IUnDo undo = Substitute.For<IUnDo>();
undo.Description.Returns("test");

Check.That(value.TryMerge(new GroupUnDo("test", undo), out _)).IsFalse();
}

[Fact]
public void TryMerge_Should_return_false_When_setter_are_different()
{
IMergeableUnDo value = new ValueUnDo<int>("test", Substitute.For<Action<int>>(), 0, 0);

Check.That(value.TryMerge(new ValueUnDo<int>("test", Substitute.For<Action<int>>(), 0, 0), out _)).IsFalse();
}

[Fact]
public void TryMerge_Should_return_false_When_newValue_and_oldValue_are_different()
{
Action<int> setter = Substitute.For<Action<int>>();
IMergeableUnDo value = new ValueUnDo<int>("test", setter, 1, 1);

Check.That(value.TryMerge(new ValueUnDo<int>("test", setter, 2, 2), out _)).IsFalse();
}

[Fact]
public void TryMerge_Should_return_false_When_timestamp_are_too_far()
{
Action<int> setter = Substitute.For<Action<int>>();
IMergeableUnDo value = new ValueUnDo<int>("test", setter, 1, 0);

Thread.Sleep(1000);

Check.That(value.TryMerge(new ValueUnDo<int>("test", setter, 2, 1), out _)).IsFalse();
}

[Fact]
public void TryMerge_Should_return_true_When_merged_with_ValueUnDo()
{
int item = 0;
void setter(int v) => item = v;
IMergeableUnDo value = new ValueUnDo<int>("test", setter, 1, 0);

Check.That(value.TryMerge(new ValueUnDo<int>("test", setter, 2, 1), out IUnDo merged)).IsTrue();
Check.That(merged.Description).IsEqualTo(value.Description);

merged.Do();
Check.That(item).IsEqualTo(2);
merged.Undo();
Check.That(item).IsEqualTo(0);
}

[Fact]
public void TryMerge_Should_return_true_When_merged_with_GroupUnDo()
{
int item = 0;
void setter(int v) => item = v;
IMergeableUnDo value = new ValueUnDo<int>("test", setter, 1, 0);

Check.That(value.TryMerge(new GroupUnDo("test", new ValueUnDo<int>("test", setter, 2, 1)), out IUnDo merged)).IsTrue();
Check.That(merged.Description).IsEqualTo(value.Description);

merged.Do();
Check.That(item).IsEqualTo(2);
merged.Undo();
Check.That(item).IsEqualTo(0);
}

#endregion
}
}
1 change: 1 addition & 0 deletions source/DefaultUnDo/DefaultUnDo.Release.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<PackageReleaseNotes>
added Description property on IUnDo
added maxCapacity on UnDoManager constructor
added IMergeableUnDo interface for mergeable operation
</PackageReleaseNotes>
</PropertyGroup>
</Project>
44 changes: 27 additions & 17 deletions source/DefaultUnDo/GroupUnDo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public GroupUnDo(string description, params IUnDo[] commands)
_description = description ?? string.Empty;
_commands = commands ?? throw new ArgumentNullException(nameof(commands));

if (_commands.Length is 0)
{
throw new ArgumentException("IUnDo sequence contains no elements.", nameof(commands));
}
if (_commands.Any(i => i is null))
{
throw new ArgumentException("IUnDo sequence contains null elements.", nameof(commands));
Expand All @@ -47,31 +51,37 @@ public GroupUnDo(params IUnDo[] commands)

#endregion

public bool IsSingle(out IUnDo command)
#region Method

/// <summary>
/// Gets the single <typeparamref name="T"/> of this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="command">When this method returns, the single <typeparamref name="T"/> of this instance, if it was its only command; otherwise, the default value for the type <typeparamref name="T"/>.
/// This parameter is passed uninitialized.</param>
/// <returns>true if the current instance contains exactly one <typeparamref name="T"/>; otherwise false.</returns>
public bool TryGetSingle<T>(out T command)
where T : IUnDo
{
command = _commands.Length is 1 ? _commands[0] : null;
command = _commands.Length is 1 && _commands[0] is T t ? t : default;

return command != null;
}

#endregion

#region IMergeableUnDo

bool IMergeableUnDo.TryMerge(IUnDo command, out IUnDo mergedCommand)
bool IMergeableUnDo.TryMerge(IUnDo other, out IUnDo mergedCommand)
{
if (_description == command.Description && _commands.Length is 1 && _commands[0] is IMergeableUnDo mergeable)
{
if (mergeable.TryMerge(command, out mergedCommand))
{
mergedCommand = new GroupUnDo(_description, mergedCommand);
return true;
}
}
else
{
mergedCommand = default;
}

return false;
mergedCommand =
_description == other.Description
&& TryGetSingle(out IMergeableUnDo mergeable)
&& mergeable.TryMerge(other, out mergedCommand)
? new GroupUnDo(_description, mergedCommand)
: null;

return mergedCommand != null;
}

#endregion
Expand Down
17 changes: 11 additions & 6 deletions source/DefaultUnDo/IMergeableUnDo.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace DefaultUnDo
namespace DefaultUnDo
{
/// <summary>
/// Provides a method to try to merge two <see cref="IUnDo"/> instances into a single one.
/// </summary>
public interface IMergeableUnDo : IUnDo
{
bool TryMerge(IUnDo command, out IUnDo mergedCommand);
/// <summary>
/// Merges the current instance with the specified <see cref="IUnDo"/>.
/// </summary>
/// <param name="other">The other <see cref="IUnDo"/> instance the current one should try to merge with.</param>
/// <param name="mergedCommand">The resulting merged <see cref="IUnDo"/> instance if the operation was successful.</param>
/// <returns>true if the merge was successful; otherwise false.</returns>
bool TryMerge(IUnDo other, out IUnDo mergedCommand);
}
}
25 changes: 12 additions & 13 deletions source/DefaultUnDo/Technical/UnDoBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,25 +96,24 @@ public int Push(IUnDo command, int doVersion, int undoVersion)
if (_buffer[_current].Command is IMergeableUnDo mergeable
&& mergeable.TryMerge(command, out IUnDo mergedCommand))
{
_buffer[_current] = new Operation(mergedCommand, doVersion, _buffer[_current].UndoVersion);
return doVersion;
command = mergedCommand;
undoVersion = _buffer[_current].UndoVersion;
}

if (++_current >= _buffer.Length)
else
{
_current -= _buffer.Length;
}
if (++_current >= _buffer.Length)
{
_current -= _buffer.Length;
}

if (_tail == _current && ++_tail >= _buffer.Length)
{
_tail -= _buffer.Length;
if (_tail == _current && ++_tail >= _buffer.Length)
{
_tail -= _buffer.Length;
}
}
}
else
{
_hasOperation = true;
}

_hasOperation = true;
_currentState = true;
_buffer[_current] = new Operation(command, doVersion, undoVersion);
_head = _current;
Expand Down
8 changes: 3 additions & 5 deletions source/DefaultUnDo/Technical/UnDoStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,11 @@ public int Push(IUnDo command, int doVersion, int undoVersion)
&& _doneOperations.Peek().Command is IMergeableUnDo mergeable
&& mergeable.TryMerge(command, out IUnDo mergedCommand))
{
_doneOperations.Push(new Operation(mergedCommand, doVersion, _doneOperations.Pop().UndoVersion));
}
else
{
_doneOperations.Push(new Operation(command, doVersion, undoVersion));
command = mergedCommand;
undoVersion = _doneOperations.Pop().UndoVersion;
}

_doneOperations.Push(new Operation(command, doVersion, undoVersion));
_undoneOperations.Clear();

return doVersion;
Expand Down
Loading

0 comments on commit a6950c1

Please sign in to comment.