Skip to content

Commit 51d9200

Browse files
committed
feat(table): add InsertRow and InsertRows methods
Add InsertRow(int, string[]), InsertRow(int, TableRow), and InsertRows(int, IEnumerable<TableRow>) for inserting rows at a specific index with selection adjustment and cache invalidation. Also fix pre-existing bug: AddRow, AddRows, and RemoveRow now invalidate _sortIndexMap and _filterIndexMap on mutation.
1 parent e3d4cee commit 51d9200

2 files changed

Lines changed: 296 additions & 0 deletions

File tree

SharpConsoleUI.Tests/Controls/TableControlTests.cs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,209 @@ public void RemoveRow_RemovesAtIndex()
249249
Assert.Equal("Row3", table.Rows[1].Cells[0]);
250250
}
251251

252+
[Fact]
253+
public void InsertRow_AtBeginning_InsertsAtIndexZero()
254+
{
255+
// Arrange
256+
var table = new TableControl();
257+
table.AddColumn("Name");
258+
table.AddRow("Row1");
259+
table.AddRow("Row2");
260+
261+
// Act
262+
table.InsertRow(0, "Inserted");
263+
264+
// Assert
265+
Assert.Equal(3, table.RowCount);
266+
Assert.Equal("Inserted", table.Rows[0].Cells[0]);
267+
Assert.Equal("Row1", table.Rows[1].Cells[0]);
268+
Assert.Equal("Row2", table.Rows[2].Cells[0]);
269+
}
270+
271+
[Fact]
272+
public void InsertRow_InMiddle_InsertsAtCorrectPosition()
273+
{
274+
// Arrange
275+
var table = new TableControl();
276+
table.AddColumn("Name");
277+
table.AddRow("Row1");
278+
table.AddRow("Row2");
279+
table.AddRow("Row3");
280+
281+
// Act
282+
table.InsertRow(1, "Inserted");
283+
284+
// Assert
285+
Assert.Equal(4, table.RowCount);
286+
Assert.Equal("Row1", table.Rows[0].Cells[0]);
287+
Assert.Equal("Inserted", table.Rows[1].Cells[0]);
288+
Assert.Equal("Row2", table.Rows[2].Cells[0]);
289+
Assert.Equal("Row3", table.Rows[3].Cells[0]);
290+
}
291+
292+
[Fact]
293+
public void InsertRow_AtEnd_AppendsRow()
294+
{
295+
// Arrange
296+
var table = new TableControl();
297+
table.AddColumn("Name");
298+
table.AddRow("Row1");
299+
300+
// Act
301+
table.InsertRow(1, "Row2");
302+
303+
// Assert
304+
Assert.Equal(2, table.RowCount);
305+
Assert.Equal("Row1", table.Rows[0].Cells[0]);
306+
Assert.Equal("Row2", table.Rows[1].Cells[0]);
307+
}
308+
309+
[Fact]
310+
public void InsertRow_IntoEmptyTable_AddsRow()
311+
{
312+
// Arrange
313+
var table = new TableControl();
314+
table.AddColumn("Name");
315+
316+
// Act
317+
table.InsertRow(0, "Only");
318+
319+
// Assert
320+
Assert.Single(table.Rows);
321+
Assert.Equal("Only", table.Rows[0].Cells[0]);
322+
}
323+
324+
[Fact]
325+
public void InsertRow_WithTableRow_InsertsAtIndex()
326+
{
327+
// Arrange
328+
var table = new TableControl();
329+
table.AddColumn("Name");
330+
table.AddRow("Row1");
331+
table.AddRow("Row2");
332+
333+
var row = new TableRow("Inserted") { Tag = "tagged" };
334+
335+
// Act
336+
table.InsertRow(1, row);
337+
338+
// Assert
339+
Assert.Equal(3, table.RowCount);
340+
Assert.Equal("Inserted", table.Rows[1].Cells[0]);
341+
Assert.Equal("tagged", table.Rows[1].Tag);
342+
}
343+
344+
[Fact]
345+
public void InsertRow_BeforeSelection_ShiftsSelectionForward()
346+
{
347+
// Arrange
348+
var table = new TableControl { ReadOnly = false };
349+
table.AddColumn("Name");
350+
table.AddRow("Row0");
351+
table.AddRow("Row1");
352+
table.AddRow("Row2");
353+
table.SelectedRowIndex = 1; // "Row1"
354+
355+
// Act
356+
table.InsertRow(0, "Inserted");
357+
358+
// Assert — selection should now point to index 2 (still "Row1")
359+
Assert.Equal(2, table.SelectedRowIndex);
360+
}
361+
362+
[Fact]
363+
public void InsertRow_AfterSelection_SelectionUnchanged()
364+
{
365+
// Arrange
366+
var table = new TableControl { ReadOnly = false };
367+
table.AddColumn("Name");
368+
table.AddRow("Row0");
369+
table.AddRow("Row1");
370+
table.SelectedRowIndex = 0;
371+
372+
// Act
373+
table.InsertRow(1, "Inserted");
374+
375+
// Assert — selection still at 0
376+
Assert.Equal(0, table.SelectedRowIndex);
377+
}
378+
379+
[Fact]
380+
public void InsertRows_InsertsMultipleAtIndex()
381+
{
382+
// Arrange
383+
var table = new TableControl();
384+
table.AddColumn("Name");
385+
table.AddRow("Row1");
386+
table.AddRow("Row4");
387+
388+
var newRows = new[]
389+
{
390+
new TableRow("Row2"),
391+
new TableRow("Row3")
392+
};
393+
394+
// Act
395+
table.InsertRows(1, newRows);
396+
397+
// Assert
398+
Assert.Equal(4, table.RowCount);
399+
Assert.Equal("Row1", table.Rows[0].Cells[0]);
400+
Assert.Equal("Row2", table.Rows[1].Cells[0]);
401+
Assert.Equal("Row3", table.Rows[2].Cells[0]);
402+
Assert.Equal("Row4", table.Rows[3].Cells[0]);
403+
}
404+
405+
[Fact]
406+
public void InsertRows_BeforeSelection_ShiftsSelectionByCount()
407+
{
408+
// Arrange
409+
var table = new TableControl { ReadOnly = false };
410+
table.AddColumn("Name");
411+
table.AddRow("Row0");
412+
table.AddRow("Row1");
413+
table.SelectedRowIndex = 1;
414+
415+
var newRows = new[] { new TableRow("A"), new TableRow("B"), new TableRow("C") };
416+
417+
// Act
418+
table.InsertRows(0, newRows);
419+
420+
// Assert — selection shifted forward by 3
421+
Assert.Equal(4, table.SelectedRowIndex);
422+
}
423+
424+
[Fact]
425+
public void InsertRow_IndexBeyondCount_ClampsToEnd()
426+
{
427+
// Arrange
428+
var table = new TableControl();
429+
table.AddColumn("Name");
430+
table.AddRow("Row1");
431+
432+
// Act — index 99 should clamp to 1 (append)
433+
table.InsertRow(99, "Appended");
434+
435+
// Assert
436+
Assert.Equal(2, table.RowCount);
437+
Assert.Equal("Row1", table.Rows[0].Cells[0]);
438+
Assert.Equal("Appended", table.Rows[1].Cells[0]);
439+
}
440+
441+
[Fact]
442+
public void InsertRow_WithDataSource_ThrowsInvalidOperation()
443+
{
444+
// Arrange
445+
var table = new TableControl();
446+
table.AddColumn("Name");
447+
table.DataSource = new TestDataSource(
448+
new[] { "Name" },
449+
new[,] { { "a" } });
450+
451+
// Act & Assert
452+
Assert.Throws<InvalidOperationException>(() => table.InsertRow(0, "fail"));
453+
}
454+
252455
[Fact]
253456
public void ClearRows_RemovesAllRows()
254457
{

SharpConsoleUI/Controls/TableControl/TableControl.cs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,8 @@ public void AddRow(params string[] cells)
522522
if (_dataSource != null)
523523
throw new InvalidOperationException("Cannot add rows when DataSource is set.");
524524
lock (_tableLock) { _rows.Add(new TableRow(cells)); }
525+
_sortIndexMap = null;
526+
_filterIndexMap = null;
525527
InvalidateColumnWidths();
526528
_measurementCache.InvalidateCache();
527529
Container?.Invalidate(true);
@@ -535,6 +537,8 @@ public void AddRow(TableRow row)
535537
if (_dataSource != null)
536538
throw new InvalidOperationException("Cannot add rows when DataSource is set.");
537539
lock (_tableLock) { _rows.Add(row); }
540+
_sortIndexMap = null;
541+
_filterIndexMap = null;
538542
InvalidateColumnWidths();
539543
_measurementCache.InvalidateCache();
540544
Container?.Invalidate(true);
@@ -548,11 +552,98 @@ public void AddRows(IEnumerable<TableRow> rows)
548552
if (_dataSource != null)
549553
throw new InvalidOperationException("Cannot add rows when DataSource is set.");
550554
lock (_tableLock) { _rows.AddRange(rows); }
555+
_sortIndexMap = null;
556+
_filterIndexMap = null;
557+
InvalidateColumnWidths();
558+
_measurementCache.InvalidateCache();
559+
Container?.Invalidate(true);
560+
}
561+
562+
/// <summary>
563+
/// Inserts a row with the specified cells at the given index.
564+
/// Index is clamped to [0, RowCount].
565+
/// </summary>
566+
/// <param name="index">The zero-based index at which to insert.</param>
567+
/// <param name="cells">The cell values for the new row.</param>
568+
public void InsertRow(int index, params string[] cells)
569+
{
570+
InsertRow(index, new TableRow(cells));
571+
}
572+
573+
/// <summary>
574+
/// Inserts a row at the given index.
575+
/// Index is clamped to [0, RowCount].
576+
/// </summary>
577+
/// <param name="index">The zero-based index at which to insert.</param>
578+
/// <param name="row">The row to insert.</param>
579+
public void InsertRow(int index, TableRow row)
580+
{
581+
if (_dataSource != null)
582+
throw new InvalidOperationException("Cannot insert rows when DataSource is set.");
583+
584+
lock (_tableLock)
585+
{
586+
index = Math.Clamp(index, 0, _rows.Count);
587+
_rows.Insert(index, row);
588+
}
589+
590+
AdjustSelectionAfterInsert(index, 1);
591+
_sortIndexMap = null;
592+
_filterIndexMap = null;
593+
InvalidateColumnWidths();
594+
_measurementCache.InvalidateCache();
595+
Container?.Invalidate(true);
596+
}
597+
598+
/// <summary>
599+
/// Inserts multiple rows starting at the given index.
600+
/// Index is clamped to [0, RowCount].
601+
/// </summary>
602+
/// <param name="index">The zero-based index at which to begin inserting.</param>
603+
/// <param name="rows">The rows to insert.</param>
604+
public void InsertRows(int index, IEnumerable<TableRow> rows)
605+
{
606+
if (_dataSource != null)
607+
throw new InvalidOperationException("Cannot insert rows when DataSource is set.");
608+
609+
var rowList = rows.ToList();
610+
if (rowList.Count == 0) return;
611+
612+
lock (_tableLock)
613+
{
614+
index = Math.Clamp(index, 0, _rows.Count);
615+
_rows.InsertRange(index, rowList);
616+
}
617+
618+
AdjustSelectionAfterInsert(index, rowList.Count);
619+
_sortIndexMap = null;
620+
_filterIndexMap = null;
551621
InvalidateColumnWidths();
552622
_measurementCache.InvalidateCache();
553623
Container?.Invalidate(true);
554624
}
555625

626+
/// <summary>
627+
/// Shifts selection indices forward after rows are inserted.
628+
/// </summary>
629+
private void AdjustSelectionAfterInsert(int insertIndex, int count)
630+
{
631+
if (_selectedRowIndex >= insertIndex)
632+
{
633+
_selectedRowIndex += count;
634+
}
635+
636+
if (_selectedRowIndices.Count > 0)
637+
{
638+
var adjusted = new HashSet<int>();
639+
foreach (var idx in _selectedRowIndices)
640+
{
641+
adjusted.Add(idx >= insertIndex ? idx + count : idx);
642+
}
643+
_selectedRowIndices = adjusted;
644+
}
645+
}
646+
556647
/// <summary>
557648
/// Removes the row at the specified index.
558649
/// </summary>
@@ -582,6 +673,8 @@ public void RemoveRow(int index)
582673
}
583674
}
584675

676+
_sortIndexMap = null;
677+
_filterIndexMap = null;
585678
InvalidateColumnWidths();
586679
_measurementCache.InvalidateCache();
587680
Container?.Invalidate(true);

0 commit comments

Comments
 (0)