Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions UndoRedo.Test/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,29 @@
Assert.AreEqual(0, stack.SaveBoundaries[0].Position, "A failed load should keep the existing save boundary position");
}

[TestMethod]
public async Task UndoRedoService_SaveLoadState_ReconstructsCommandWithEmptyData()
{
// Arrange
UndoRedoService stack = CreateService();
stack.SetSerializer(new JsonUndoRedoSerializer());
stack.Execute(new EmptyDataSerializableCommand());

// Act
byte[] data = await stack.SaveStateAsync().ConfigureAwait(false);

Check warning on line 557 in UndoRedo.Test/SerializationTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_UndoRedo&issues=AaDjStDOHLoqijiDtQJB&open=AaDjStDOHLoqijiDtQJB&pullRequest=103

UndoRedoService newStack = CreateService();
newStack.SetSerializer(new JsonUndoRedoSerializer());
bool success = await newStack.LoadStateAsync(data).ConfigureAwait(false);

Check warning on line 561 in UndoRedo.Test/SerializationTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_UndoRedo&issues=AaDjStDOHLoqijiDtQJC&open=AaDjStDOHLoqijiDtQJC&pullRequest=103

// Assert: SerializeData() returning "" is a normal result for a command with no parameters,
// so the command is rebuilt rather than replaced with an un-undoable placeholder
Assert.IsTrue(success);
Assert.IsInstanceOfType<EmptyDataSerializableCommand>(newStack.Commands[0]);
Assert.IsTrue(await newStack.UndoAsync().ConfigureAwait(false));

Check warning on line 567 in UndoRedo.Test/SerializationTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_UndoRedo&issues=AaDjStDOHLoqijiDtQJD&open=AaDjStDOHLoqijiDtQJD&pullRequest=103
Assert.AreEqual(-1, newStack.CurrentPosition);
}

private const string MalformedAssemblyName = "malformed assembly name";
private const string InvalidVersion = "invalid assembly version";
private const string NotACommand = "serializable type that is not a command";
Expand Down Expand Up @@ -650,6 +673,32 @@
}
#pragma warning restore CA1812

private sealed class EmptyDataSerializableCommand : BaseCommand, ISerializableCommand
{
public EmptyDataSerializableCommand() : base(ChangeType.Modify, ["test"])
{
}

public override string Description => "Clear all";

public override void Execute()
{
// Test implementation
}

public override void Undo()
{
// Test implementation
}

public string SerializeData() => string.Empty;

public void DeserializeData(string data)
{
// Nothing to restore
}
}

private sealed class ConstructorOnlySerializableCommand(string value)
: BaseCommand(ChangeType.Modify, ["test"]), ISerializableCommand
{
Expand Down
7 changes: 5 additions & 2 deletions UndoRedo/Services/JsonUndoRedoSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ private static ICommand ConvertFromSerializableCommand(SerializableCommand seria
{
// This is a simplified approach - real implementations would need a factory pattern
// or registry to recreate commands from serialized data
if (string.IsNullOrEmpty(serializableCommand.Data))
// A null Data is what ConvertToSerializableCommand writes for a command that is not an
// ISerializableCommand. An empty string is not that marker: it is a normal SerializeData()
// result for a command with no parameters, and such a command is reconstructed below.
if (serializableCommand.Data is null)
{
// Return a placeholder command that can't execute but preserves metadata
return new PlaceholderCommand(serializableCommand.Description, serializableCommand.NavigationContext, serializableCommand.Metadata);
Expand Down Expand Up @@ -171,7 +174,7 @@ private static ICommand ConvertFromSerializableCommand(SerializableCommand seria

try
{
instance?.DeserializeData(serializableCommand.Data!);
instance?.DeserializeData(serializableCommand.Data);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex) when (ex is not OperationCanceledException)
Expand Down
Loading