Files
radzen-blazor/Radzen.Blazor.Tests/Spreadsheet/RowColumnCommandTests.cs
Atanas Korchev 96f6c79fc4 Refactor spreadsheet command and overlay shapes
Three coupled refactors that share callers and tests:

1. Anchored drawing commands: IAnchoredDrawing interface; replace four
   near-identical Move/Resize Chart/Image commands with generic
   MoveAnchoredCommand<T> and ResizeAnchoredCommand<T>. Fixes dead-code
   null check in Unexecute and adds the missing null guard in the
   TwoCellAnchor Resize ctor.

2. Drawing overlay base: extract DrawingOverlayBase<T> covering ~95%
   duplication between ChartOverlay and ImageOverlay. Fix four buggy
   resize-handle predicates while consolidating (e gated on bottom,
   w gated on bottom, etc).

3. Insert/Delete row/column commands: collapse 4 nearly-identical
   InsertRow*/InsertColumn* commands to InsertRowCommand and
   InsertColumnCommand (caller adds +1 for 'after' semantics).
   Replace whole-sheet snapshot inverse (SheetSnapshotCommandBase)
   with a surgical inverse via new DeleteRangeCommandBase.
   Delete the dead SheetSnapshotCommandBase.

The shared RadzenSpreadsheet.razor.cs callers and the test files
touch both #1 and #3, hence the combined commit.
2026-06-15 18:45:16 +03:00

313 lines
11 KiB
C#

using Xunit;
using Radzen.Documents.Spreadsheet;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class RowColumnCommandTests
{
[Fact]
public void DeleteRowsCommand_SingleRow_ExecuteAndUndo_RestoresState()
{
var sheet = new Worksheet(4, 3);
var view = new SheetView(sheet);
sheet.Cells[0, 0].Value = "R1";
sheet.Cells[1, 0].Value = "R2";
sheet.Cells[2, 0].Value = "R3";
sheet.Cells[3, 0].Value = "R4";
var cmd = new DeleteRowsCommand(sheet, 1, 1);
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(3, sheet.RowCount);
Assert.Equal("R1", sheet.Cells[0, 0].Value);
Assert.Equal("R3", sheet.Cells[1, 0].Value);
Assert.Equal("R4", sheet.Cells[2, 0].Value);
view.Commands.Undo();
Assert.Equal(4, sheet.RowCount);
Assert.Equal("R1", sheet.Cells[0, 0].Value);
Assert.Equal("R2", sheet.Cells[1, 0].Value);
Assert.Equal("R3", sheet.Cells[2, 0].Value);
Assert.Equal("R4", sheet.Cells[3, 0].Value);
}
[Fact]
public void DeleteRowsCommand_ExecuteAndUndo_RestoresState()
{
var sheet = new Worksheet(6, 3);
var view = new SheetView(sheet);
sheet.Cells[0, 0].Value = "R1";
sheet.Cells[1, 0].Value = "R2";
sheet.Cells[2, 0].Value = "R3";
sheet.Cells[3, 0].Value = "R4";
sheet.Cells[4, 0].Value = "R5";
sheet.Cells[5, 0].Value = "R6";
var cmd = new DeleteRowsCommand(sheet, 1, 3); // delete rows 2..4
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(3, sheet.RowCount);
Assert.Equal("R1", sheet.Cells[0, 0].Value);
Assert.Equal("R5", sheet.Cells[1, 0].Value);
Assert.Equal("R6", sheet.Cells[2, 0].Value);
view.Commands.Undo();
Assert.Equal(6, sheet.RowCount);
Assert.Equal("R1", sheet.Cells[0, 0].Value);
Assert.Equal("R2", sheet.Cells[1, 0].Value);
Assert.Equal("R3", sheet.Cells[2, 0].Value);
Assert.Equal("R4", sheet.Cells[3, 0].Value);
Assert.Equal("R5", sheet.Cells[4, 0].Value);
Assert.Equal("R6", sheet.Cells[5, 0].Value);
}
[Fact]
public void InsertRowAfterCommand_ExecuteAndUndo_RestoresState()
{
var sheet = new Worksheet(4, 3);
var view = new SheetView(sheet);
sheet.Cells[1, 0].Value = 1d; // A2
sheet.Cells[1, 1].Formula = "=A2+10"; // B2
var cmd = new InsertRowCommand(sheet, 2); // after row 1 -> insert at index 2
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(5, sheet.RowCount);
// Inserting after row 1 does not move row 1; A2 and B2 stay
Assert.Equal(1d, sheet.Cells[1, 0].Value);
Assert.Equal("=A2+10", sheet.Cells[1, 1].Formula);
Assert.Equal(11d, sheet.Cells[1, 1].Value);
view.Commands.Undo();
Assert.Equal(4, sheet.RowCount);
Assert.Equal(1d, sheet.Cells[1, 0].Value);
Assert.Equal("=A2+10", sheet.Cells[1, 1].Formula);
}
[Fact]
public void InsertRowBeforeCommand_ExecuteAndUndo_RestoresState()
{
var sheet = new Worksheet(4, 3);
var view = new SheetView(sheet);
sheet.Cells[1, 0].Value = 1d; // A2
sheet.Cells[1, 1].Formula = "=A2+10"; // B2
var cmd = new InsertRowCommand(sheet, 1); // before row 1 -> insert at index 1
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(5, sheet.RowCount);
// value shifted down (A2 becomes A3)
Assert.Equal(1d, sheet.Cells[2, 0].Value);
// formula shifted down and reference updated A2->A3
Assert.Equal("=A3+10", sheet.Cells[2, 1].Formula);
Assert.Equal(11d, sheet.Cells[2, 1].Value);
view.Commands.Undo();
Assert.Equal(4, sheet.RowCount);
Assert.Equal(1d, sheet.Cells[1, 0].Value);
Assert.Equal("=A2+10", sheet.Cells[1, 1].Formula);
}
[Fact]
public void InsertRowAfterCommand_WithMultiSelection_InsertsAfterLastRow()
{
var sheet = new Worksheet(6, 2);
var view = new SheetView(sheet);
// Mark rows with their index in A
for (int r = 0; r < 6; r++) sheet.Cells[r, 0].Value = r + 1;
// Simulate selection rows 1..3 (0-based), last is 3 -> insert after 3 at index 4
var cmd = new InsertRowCommand(sheet, 4);
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(7, sheet.RowCount);
// Inserted row at index 4 is empty
Assert.Null(sheet.Cells[4, 0].Value);
// Row 5 takes previous row 4 value (5)
Assert.Equal(5d, sheet.Cells[5, 0].Value);
// Row 6 takes previous row 5 value (6)
Assert.Equal(6d, sheet.Cells[6, 0].Value);
view.Commands.Undo();
Assert.Equal(6, sheet.RowCount);
Assert.Equal(5d, sheet.Cells[4, 0].Value);
}
[Fact]
public void InsertRowBeforeCommand_WithMultiSelection_InsertsBeforeFirstRow()
{
var sheet = new Worksheet(6, 2);
var view = new SheetView(sheet);
for (int r = 0; r < 6; r++) sheet.Cells[r, 0].Value = r + 1;
// Simulate selection rows 2..4 (first is 2) -> insert at index 2
var cmd = new InsertRowCommand(sheet, 2);
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(7, sheet.RowCount);
// Inserted row at index 2 is empty
Assert.Null(sheet.Cells[2, 0].Value);
// Row 3 takes previous row 2 value (3)
Assert.Equal(3d, sheet.Cells[3, 0].Value);
view.Commands.Undo();
Assert.Equal(6, sheet.RowCount);
Assert.Equal(3d, sheet.Cells[2, 0].Value);
}
[Fact]
public void InsertColumnAfterCommand_WithMultiSelection_InsertsAfterLastColumn()
{
var sheet = new Worksheet(2, 6);
var view = new SheetView(sheet);
// Mark columns with their index in row 1
for (int c = 0; c < 6; c++) sheet.Cells[0, c].Value = c + 1;
// Simulate selection columns 1..3 (last is 3) -> insert after col 3 at index 4
var cmd = new InsertColumnCommand(sheet, 4);
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(7, sheet.ColumnCount);
// Inserted column at index 4 is empty
Assert.Null(sheet.Cells[0, 4].Value);
// Column 5 takes previous column 4 value (5)
Assert.Equal(5d, sheet.Cells[0, 5].Value);
view.Commands.Undo();
Assert.Equal(6, sheet.ColumnCount);
Assert.Equal(5d, sheet.Cells[0, 4].Value);
}
[Fact]
public void InsertColumnBeforeCommand_WithMultiSelection_InsertsBeforeFirstColumn()
{
var sheet = new Worksheet(2, 6);
var view = new SheetView(sheet);
for (int c = 0; c < 6; c++) sheet.Cells[0, c].Value = c + 1;
// Simulate selection columns 2..4 (first is 2) -> insert at index 2
var cmd = new InsertColumnCommand(sheet, 2);
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(7, sheet.ColumnCount);
// Inserted column at index 2 is empty
Assert.Null(sheet.Cells[0, 2].Value);
// Column 3 takes previous column 2 value (3)
Assert.Equal(3d, sheet.Cells[0, 3].Value);
view.Commands.Undo();
Assert.Equal(6, sheet.ColumnCount);
Assert.Equal(3d, sheet.Cells[0, 2].Value);
}
[Fact]
public void DeleteColumnsCommand_SingleColumn_ExecuteAndUndo_RestoresState()
{
var sheet = new Worksheet(3, 4);
var view = new SheetView(sheet);
sheet.Cells[0, 0].Value = "A";
sheet.Cells[0, 1].Value = "B";
sheet.Cells[0, 2].Value = "C";
sheet.Cells[0, 3].Value = "D";
var cmd = new DeleteColumnsCommand(sheet, 1, 1);
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(3, sheet.ColumnCount);
Assert.Equal("A", sheet.Cells[0, 0].Value);
Assert.Equal("C", sheet.Cells[0, 1].Value);
Assert.Equal("D", sheet.Cells[0, 2].Value);
view.Commands.Undo();
Assert.Equal(4, sheet.ColumnCount);
Assert.Equal("A", sheet.Cells[0, 0].Value);
Assert.Equal("B", sheet.Cells[0, 1].Value);
Assert.Equal("C", sheet.Cells[0, 2].Value);
Assert.Equal("D", sheet.Cells[0, 3].Value);
}
[Fact]
public void DeleteColumnsCommand_ExecuteAndUndo_RestoresState()
{
var sheet = new Worksheet(3, 6);
var view = new SheetView(sheet);
sheet.Cells[0, 0].Value = "A";
sheet.Cells[0, 1].Value = "B";
sheet.Cells[0, 2].Value = "C";
sheet.Cells[0, 3].Value = "D";
sheet.Cells[0, 4].Value = "E";
sheet.Cells[0, 5].Value = "F";
var cmd = new DeleteColumnsCommand(sheet, 1, 3); // delete B..D
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(3, sheet.ColumnCount);
Assert.Equal("A", sheet.Cells[0, 0].Value);
Assert.Equal("E", sheet.Cells[0, 1].Value);
Assert.Equal("F", sheet.Cells[0, 2].Value);
view.Commands.Undo();
Assert.Equal(6, sheet.ColumnCount);
Assert.Equal("A", sheet.Cells[0, 0].Value);
Assert.Equal("B", sheet.Cells[0, 1].Value);
Assert.Equal("C", sheet.Cells[0, 2].Value);
Assert.Equal("D", sheet.Cells[0, 3].Value);
Assert.Equal("E", sheet.Cells[0, 4].Value);
Assert.Equal("F", sheet.Cells[0, 5].Value);
}
[Fact]
public void InsertColumnBeforeCommand_ExecuteAndUndo_RestoresState()
{
var sheet = new Worksheet(5, 5);
var view = new SheetView(sheet);
sheet.Cells[1, 0].Value = 1d; // A2
sheet.Cells[1, 1].Formula = "=A2+10"; // B2
var cmd = new InsertColumnCommand(sheet, 0); // before A
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(6, sheet.ColumnCount);
// value shifted right (A2 becomes B2)
Assert.Equal(1d, sheet.Cells[1, 1].Value);
// formula shifted right and reference updated A2->B2
Assert.Equal("=B2+10", sheet.Cells[1, 2].Formula); // original B2 moved to C2
Assert.Equal(11d, sheet.Cells[1, 2].Value);
view.Commands.Undo();
Assert.Equal(5, sheet.ColumnCount);
Assert.Equal(1d, sheet.Cells[1, 0].Value);
Assert.Equal("=A2+10", sheet.Cells[1, 1].Formula);
}
[Fact]
public void InsertColumnAfterCommand_ExecuteAndUndo_RestoresState()
{
var sheet = new Worksheet(5, 5);
var view = new SheetView(sheet);
sheet.Cells[1, 0].Value = 1d; // A2
sheet.Cells[1, 1].Formula = "=A2+10"; // B2
var cmd = new InsertColumnCommand(sheet, 1); // after A
Assert.True(view.Commands.Execute(cmd));
Assert.Equal(6, sheet.ColumnCount);
// value at A2 stays, but B2 moves to C2; formula references should update if referencing >= inserted column
Assert.Equal(1d, sheet.Cells[1, 0].Value);
Assert.Equal("=A2+10", sheet.Cells[1, 2].Formula); // original B2 moved to C2
Assert.Equal(11d, sheet.Cells[1, 2].Value);
view.Commands.Undo();
Assert.Equal(5, sheet.ColumnCount);
Assert.Equal(1d, sheet.Cells[1, 0].Value);
Assert.Equal("=A2+10", sheet.Cells[1, 1].Formula);
}
}