diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 69b9b0c..1daae09 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -89,7 +89,7 @@ jobs:
shell: bash
run: |
dotnet tool restore
- dotnet example --all
+ dotnet example --all --skip live --skip livetable --skip prompt
- name: Build
shell: bash
diff --git a/dotnet-tools.json b/dotnet-tools.json
index 000f1c1..7105b84 100644
--- a/dotnet-tools.json
+++ b/dotnet-tools.json
@@ -15,7 +15,7 @@
]
},
"dotnet-example": {
- "version": "1.3.1",
+ "version": "1.5.0",
"commands": [
"dotnet-example"
]
diff --git a/examples/Console/Live/Live.csproj b/examples/Console/Live/Live.csproj
index c8cb17b..b2e2b58 100644
--- a/examples/Console/Live/Live.csproj
+++ b/examples/Console/Live/Live.csproj
@@ -5,7 +5,7 @@
net5.0
Live
Demonstrates how to do live updates.
- Misc
+ Live
diff --git a/examples/Console/LiveTable/LiveTable.csproj b/examples/Console/LiveTable/LiveTable.csproj
new file mode 100644
index 0000000..65befd0
--- /dev/null
+++ b/examples/Console/LiveTable/LiveTable.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net5.0
+ LiveTable
+ Demonstrates how to do live updates in a table.
+ Live
+
+
+
+
+
+
+
diff --git a/examples/Console/LiveTable/Program.cs b/examples/Console/LiveTable/Program.cs
new file mode 100644
index 0000000..2d61aa2
--- /dev/null
+++ b/examples/Console/LiveTable/Program.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Spectre.Console.Examples
+{
+ public static class Program
+ {
+ private const int NumberOfRows = 10;
+
+ private static readonly Random _random = new();
+ private static readonly string[] _exchanges = new string[]
+ {
+ "SGD", "SEK", "PLN",
+ "MYR", "EUR", "USD",
+ "AUD", "JPY", "CNH",
+ "HKD", "CAD", "INR",
+ "DKK", "GBP", "RUB",
+ "NZD", "MXN", "IDR",
+ "TWD", "THB", "VND",
+ };
+
+ public static async Task Main(string[] args)
+ {
+ var table = new Table().Expand().BorderColor(Color.Grey);
+ table.AddColumn("[yellow]Source currency[/]");
+ table.AddColumn("[yellow]Destination currency[/]");
+ table.AddColumn("[yellow]Exchange rate[/]");
+
+ AnsiConsole.MarkupLine("Press [yellow]CTRL+C[/] to exit");
+
+ await AnsiConsole.Live(table)
+ .AutoClear(false)
+ .Overflow(VerticalOverflow.Ellipsis)
+ .Cropping(VerticalOverflowCropping.Bottom)
+ .StartAsync(async ctx =>
+ {
+ // Add some initial rows
+ foreach (var _ in Enumerable.Range(0, NumberOfRows))
+ {
+ AddExchangeRateRow(table);
+ }
+
+ // Continously update the table
+ while (true)
+ {
+ // More rows than we want?
+ if (table.Rows.Count > NumberOfRows)
+ {
+ // Remove the first one
+ table.Rows.RemoveAt(0);
+ }
+
+ // Add a new row
+ AddExchangeRateRow(table);
+
+ // Refresh and wait for a while
+ ctx.Refresh();
+ await Task.Delay(400);
+ }
+ });
+ }
+
+ private static void AddExchangeRateRow(Table table)
+ {
+ var (source, destination, rate) = GetExchangeRate();
+ table.AddRow(
+ source, destination,
+ _random.NextDouble() > 0.35D ? $"[green]{rate}[/]" : $"[red]{rate}[/]");
+ }
+
+ private static (string Source, string Destination, double Rate) GetExchangeRate()
+ {
+ var source = _exchanges[_random.Next(0, _exchanges.Length)];
+ var dest = _exchanges[_random.Next(0, _exchanges.Length)];
+ var rate = 200 / ((_random.NextDouble() * 320) + 1);
+
+ while (source == dest)
+ {
+ dest = _exchanges[_random.Next(0, _exchanges.Length)];
+ }
+
+ return (source, dest, rate);
+ }
+ }
+}
diff --git a/examples/Examples.sln b/examples/Examples.sln
index ffbfab3..66a7094 100644
--- a/examples/Examples.sln
+++ b/examples/Examples.sln
@@ -63,6 +63,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tables", "Console\Tables\Ta
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Trees", "Console\Trees\Trees.csproj", "{2BD88288-E05D-4978-B045-17937078E63C}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiveTable", "Console\LiveTable\LiveTable.csproj", "{E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -409,6 +411,18 @@ Global
{2BD88288-E05D-4978-B045-17937078E63C}.Release|x64.Build.0 = Release|Any CPU
{2BD88288-E05D-4978-B045-17937078E63C}.Release|x86.ActiveCfg = Release|Any CPU
{2BD88288-E05D-4978-B045-17937078E63C}.Release|x86.Build.0 = Release|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Debug|x64.Build.0 = Debug|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Debug|x86.Build.0 = Debug|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Release|x64.ActiveCfg = Release|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Release|x64.Build.0 = Release|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Release|x86.ActiveCfg = Release|Any CPU
+ {E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Spectre.Console/Extensions/TableExtensions.cs b/src/Spectre.Console/Extensions/TableExtensions.cs
index 904c646..8f0776d 100644
--- a/src/Spectre.Console/Extensions/TableExtensions.cs
+++ b/src/Spectre.Console/Extensions/TableExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using Spectre.Console.Rendering;
@@ -35,6 +36,28 @@ namespace Spectre.Console
return table;
}
+ ///
+ /// Adds a row to the table.
+ ///
+ /// The table to add the row to.
+ /// The row columns to add.
+ /// The same instance so that multiple calls can be chained.
+ public static Table AddRow(this Table table, IEnumerable columns)
+ {
+ if (table is null)
+ {
+ throw new ArgumentNullException(nameof(table));
+ }
+
+ if (columns is null)
+ {
+ throw new ArgumentNullException(nameof(columns));
+ }
+
+ table.Rows.Add(new TableRow(columns));
+ return table;
+ }
+
///
/// Adds a row to the table.
///
@@ -48,7 +71,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(table));
}
- return table.AddRow(columns);
+ return table.AddRow((IEnumerable)columns);
}
///
@@ -143,6 +166,80 @@ namespace Spectre.Console
return table;
}
+ ///
+ /// Inserts a row in the table at the specified index.
+ ///
+ /// The table to add the row to.
+ /// The index to insert the row at.
+ /// The row columns to add.
+ /// The same instance so that multiple calls can be chained.
+ public static Table InsertRow(this Table table, int index, IEnumerable columns)
+ {
+ if (table is null)
+ {
+ throw new ArgumentNullException(nameof(table));
+ }
+
+ if (columns is null)
+ {
+ throw new ArgumentNullException(nameof(columns));
+ }
+
+ table.Rows.Insert(index, new TableRow(columns));
+ return table;
+ }
+
+ ///
+ /// Inserts a row in the table at the specified index.
+ ///
+ /// The table to add the row to.
+ /// The index to insert the row at.
+ /// The row columns to add.
+ /// The same instance so that multiple calls can be chained.
+ public static Table InsertRow(this Table table, int index, params IRenderable[] columns)
+ {
+ if (table is null)
+ {
+ throw new ArgumentNullException(nameof(table));
+ }
+
+ return InsertRow(table, index, (IEnumerable)columns);
+ }
+
+ ///
+ /// Inserts a row in the table at the specified index.
+ ///
+ /// The table to add the row to.
+ /// The index to insert the row at.
+ /// The row columns to add.
+ /// The same instance so that multiple calls can be chained.
+ public static Table InsertRow(this Table table, int index, params string[] columns)
+ {
+ if (table is null)
+ {
+ throw new ArgumentNullException(nameof(table));
+ }
+
+ return InsertRow(table, index, columns.Select(column => new Markup(column)));
+ }
+
+ ///
+ /// Removes a row from the table with the specified index.
+ ///
+ /// The table to add the row to.
+ /// The index to remove the row at.
+ /// The same instance so that multiple calls can be chained.
+ public static Table RemoveRow(this Table table, int index)
+ {
+ if (table is null)
+ {
+ throw new ArgumentNullException(nameof(table));
+ }
+
+ table.Rows.RemoveAt(index);
+ return table;
+ }
+
///
/// Sets the table width.
///
diff --git a/src/Spectre.Console/Widgets/Table/Table.cs b/src/Spectre.Console/Widgets/Table/Table.cs
index 66d04ae..9b4fe89 100644
--- a/src/Spectre.Console/Widgets/Table/Table.cs
+++ b/src/Spectre.Console/Widgets/Table/Table.cs
@@ -11,7 +11,6 @@ namespace Spectre.Console
public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
{
private readonly List _columns;
- private readonly List _rows;
///
/// Gets the table columns.
@@ -21,7 +20,7 @@ namespace Spectre.Console
///
/// Gets the table rows.
///
- public IReadOnlyList Rows => _rows;
+ public TableRowCollection Rows { get; }
///
public TableBorder Border { get; set; } = TableBorder.Square;
@@ -81,7 +80,7 @@ namespace Spectre.Console
public Table()
{
_columns = new List();
- _rows = new List();
+ Rows = new TableRowCollection(this);
}
///
@@ -96,7 +95,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(column));
}
- if (_rows.Count > 0)
+ if (Rows.Count > 0)
{
throw new InvalidOperationException("Cannot add new columns to table with existing rows.");
}
@@ -105,36 +104,6 @@ namespace Spectre.Console
return this;
}
- ///
- /// Adds a row to the table.
- ///
- /// The row columns to add.
- /// The same instance so that multiple calls can be chained.
- public Table AddRow(IEnumerable columns)
- {
- if (columns is null)
- {
- throw new ArgumentNullException(nameof(columns));
- }
-
- var rowColumnCount = columns.GetCount();
- if (rowColumnCount > _columns.Count)
- {
- throw new InvalidOperationException("The number of row columns are greater than the number of table columns.");
- }
-
- _rows.Add(new TableRow(columns));
-
- // Need to add missing columns?
- if (rowColumnCount < _columns.Count)
- {
- var diff = _columns.Count - rowColumnCount;
- Enumerable.Range(0, diff).ForEach(_ => _rows.Last().Add(Text.Empty));
- }
-
- return this;
- }
-
///
protected override Measurement Measure(RenderContext context, int maxWidth)
{
@@ -190,7 +159,7 @@ namespace Spectre.Console
}
// Add rows
- rows.AddRange(_rows);
+ rows.AddRange(Rows);
// Show footers?
if (ShowFooters && _columns.Any(c => c.Footer != null))
diff --git a/src/Spectre.Console/Widgets/Table/TableRow.cs b/src/Spectre.Console/Widgets/Table/TableRow.cs
index 7eeb356..ce760a7 100644
--- a/src/Spectre.Console/Widgets/Table/TableRow.cs
+++ b/src/Spectre.Console/Widgets/Table/TableRow.cs
@@ -12,6 +12,11 @@ namespace Spectre.Console
{
private readonly List _items;
+ ///
+ /// Gets the number of columns in the row.
+ ///
+ public int Count => _items.Count;
+
internal bool IsHeader { get; }
internal bool IsFooter { get; }
diff --git a/src/Spectre.Console/Widgets/Table/TableRowCollection.cs b/src/Spectre.Console/Widgets/Table/TableRowCollection.cs
new file mode 100644
index 0000000..f189392
--- /dev/null
+++ b/src/Spectre.Console/Widgets/Table/TableRowCollection.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Spectre.Console.Rendering;
+
+namespace Spectre.Console
+{
+ ///
+ /// Represents a collection holding table rows.
+ ///
+ public sealed class TableRowCollection : IReadOnlyList
+ {
+ private readonly Table _table;
+ private readonly IList _list;
+ private readonly object _lock;
+
+ ///
+ TableRow IReadOnlyList.this[int index]
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _list[index];
+ }
+ }
+ }
+
+ ///
+ /// Gets the number of rows in the collection.
+ ///
+ public int Count
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _list.Count;
+ }
+ }
+ }
+
+ internal TableRowCollection(Table table)
+ {
+ _table = table ?? throw new ArgumentNullException(nameof(table));
+ _list = new List();
+ _lock = new object();
+ }
+
+ ///
+ /// Adds a new row.
+ ///
+ /// The columns that are part of the row to add.
+ /// The index of the added item.
+ public int Add(IEnumerable columns)
+ {
+ if (columns is null)
+ {
+ throw new ArgumentNullException(nameof(columns));
+ }
+
+ lock (_lock)
+ {
+ var row = CreateRow(columns);
+ _list.Add(row);
+ return _list.IndexOf(row);
+ }
+ }
+
+ ///
+ /// Inserts a new row at the specified index.
+ ///
+ /// The index to insert the row at.
+ /// The columns that are part of the row to insert.
+ /// The index of the inserted item.
+ public int Insert(int index, IEnumerable columns)
+ {
+ if (columns is null)
+ {
+ throw new ArgumentNullException(nameof(columns));
+ }
+
+ lock (_lock)
+ {
+ var row = CreateRow(columns);
+ _list.Insert(index, row);
+ return _list.IndexOf(row);
+ }
+ }
+
+ ///
+ /// Removes a row at the specified index.
+ ///
+ /// The index to remove a row at.
+ public void RemoveAt(int index)
+ {
+ lock (_lock)
+ {
+ if (index < 0)
+ {
+ throw new IndexOutOfRangeException("Table row index cannot be negative.");
+ }
+ else if (index >= _list.Count)
+ {
+ throw new IndexOutOfRangeException("Table row index cannot exceed the number of rows in the table.");
+ }
+
+ _list.RemoveAt(index);
+ }
+ }
+
+ ///
+ /// Clears all rows.
+ ///
+ public void Clear()
+ {
+ lock (_lock)
+ {
+ _list.Clear();
+ }
+ }
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ lock (_lock)
+ {
+ var items = new TableRow[_list.Count];
+ _list.CopyTo(items, 0);
+ return new TableRowEnumerator(items);
+ }
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ private TableRow CreateRow(IEnumerable columns)
+ {
+ var row = new TableRow(columns);
+
+ if (row.Count > _table.Columns.Count)
+ {
+ throw new InvalidOperationException("The number of row columns are greater than the number of table columns.");
+ }
+
+ // Need to add missing columns
+ if (row.Count < _table.Columns.Count)
+ {
+ var diff = _table.Columns.Count - row.Count;
+ Enumerable.Range(0, diff).ForEach(_ => row.Add(Text.Empty));
+ }
+
+ return row;
+ }
+ }
+}
diff --git a/src/Spectre.Console/Widgets/Table/TableRowEnumerator.cs b/src/Spectre.Console/Widgets/Table/TableRowEnumerator.cs
new file mode 100644
index 0000000..f76b7dd
--- /dev/null
+++ b/src/Spectre.Console/Widgets/Table/TableRowEnumerator.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Spectre.Console
+{
+ internal sealed class TableRowEnumerator : IEnumerator
+ {
+ private readonly TableRow[] _items;
+ private int _index;
+
+ public TableRow Current => _items[_index];
+ object? IEnumerator.Current => _items[_index];
+
+ public TableRowEnumerator(TableRow[] items)
+ {
+ _items = items ?? throw new ArgumentNullException(nameof(items));
+ _index = -1;
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ _index++;
+ return _index < _items.Length;
+ }
+
+ public void Reset()
+ {
+ _index = -1;
+ }
+ }
+}
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Add.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Add.Output.verified.txt
new file mode 100644
index 0000000..103d74a
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Add.Output.verified.txt
@@ -0,0 +1,7 @@
+┌───────────┐
+│ Column #1 │
+├───────────┤
+│ 1 │
+│ 2 │
+│ 3 │
+└───────────┘
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Add.Renderables.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Add.Renderables.verified.txt
new file mode 100644
index 0000000..0d313a3
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Add.Renderables.verified.txt
@@ -0,0 +1,7 @@
+┌───────────┬───────────┐
+│ Column #1 │ Column #2 │
+├───────────┼───────────┤
+│ 1 │ 1-2 │
+│ 2 │ 2-2 │
+│ 3 │ 3-2 │
+└───────────┴───────────┘
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Add.Strings.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Add.Strings.verified.txt
new file mode 100644
index 0000000..0d313a3
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Add.Strings.verified.txt
@@ -0,0 +1,7 @@
+┌───────────┬───────────┐
+│ Column #1 │ Column #2 │
+├───────────┼───────────┤
+│ 1 │ 1-2 │
+│ 2 │ 2-2 │
+│ 3 │ 3-2 │
+└───────────┴───────────┘
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Insert.Renderables.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Insert.Renderables.verified.txt
new file mode 100644
index 0000000..da268df
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Insert.Renderables.verified.txt
@@ -0,0 +1,7 @@
+┌───────────┬───────────┐
+│ Column #1 │ Column #2 │
+├───────────┼───────────┤
+│ 1 │ 1-2 │
+│ 3 │ 3-2 │
+│ 2 │ 2-2 │
+└───────────┴───────────┘
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Insert.Strings.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Insert.Strings.verified.txt
new file mode 100644
index 0000000..da268df
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Insert.Strings.verified.txt
@@ -0,0 +1,7 @@
+┌───────────┬───────────┐
+│ Column #1 │ Column #2 │
+├───────────┼───────────┤
+│ 1 │ 1-2 │
+│ 3 │ 3-2 │
+│ 2 │ 2-2 │
+└───────────┴───────────┘
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Remove.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Remove.Output.verified.txt
new file mode 100644
index 0000000..83d72fe
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Extensions/Remove.Output.verified.txt
@@ -0,0 +1,6 @@
+┌───────────┬───────────┐
+│ Column #1 │ Column #2 │
+├───────────┼───────────┤
+│ 1 │ 1-2 │
+│ 3 │ 3-2 │
+└───────────┴───────────┘
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Insert.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Insert.Output.verified.txt
new file mode 100644
index 0000000..154cf16
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Insert.Output.verified.txt
@@ -0,0 +1,7 @@
+┌───────────┐
+│ Column #1 │
+├───────────┤
+│ 1 │
+│ 3 │
+│ 2 │
+└───────────┘
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Remove.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Remove.Output.verified.txt
new file mode 100644
index 0000000..d13bb8c
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Rows/Remove.Output.verified.txt
@@ -0,0 +1,6 @@
+┌───────────┐
+│ Column #1 │
+├───────────┤
+│ 1 │
+│ 3 │
+└───────────┘
diff --git a/test/Spectre.Console.Tests/Spectre.Console.Tests.csproj b/test/Spectre.Console.Tests/Spectre.Console.Tests.csproj
index 3bf7cea..7b8edd7 100644
--- a/test/Spectre.Console.Tests/Spectre.Console.Tests.csproj
+++ b/test/Spectre.Console.Tests/Spectre.Console.Tests.csproj
@@ -37,6 +37,7 @@
+
diff --git a/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Injection.cs b/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Injection.cs
index 6132197..be2fd08 100644
--- a/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Injection.cs
+++ b/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Injection.cs
@@ -1,5 +1,3 @@
-using System;
-using System.Collections.Generic;
using Shouldly;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/Table/TableRowCollectionExtensionsTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/Table/TableRowCollectionExtensionsTests.cs
new file mode 100644
index 0000000..960c90a
--- /dev/null
+++ b/test/Spectre.Console.Tests/Unit/Widgets/Table/TableRowCollectionExtensionsTests.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Spectre.Console.Testing;
+using Spectre.Verify.Extensions;
+using VerifyXunit;
+using Xunit;
+
+namespace Spectre.Console.Tests.Unit
+{
+ [UsesVerify]
+ [ExpectationPath("Widgets/Table/Rows/Extensions")]
+ public sealed class TableRowCollectionExtensionsTests
+ {
+ [UsesVerify]
+ public sealed class TheAddRowMethod
+ {
+ [Fact]
+ [Expectation("Add", "Renderables")]
+ public Task Should_Add_Renderables()
+ {
+ // Given
+ var console = new TestConsole();
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.AddColumn("Column #2");
+ table.AddRow(new[] { new Text("1"), new Text("1-2") });
+ table.AddRow(new[] { new Text("2"), new Text("2-2") });
+ table.AddRow(new[] { new Text("3"), new Text("3-2") });
+
+ // When
+ console.Write(table);
+
+ // Then
+ return Verifier.Verify(console.Output);
+ }
+
+ [Fact]
+ [Expectation("Add", "Strings")]
+ public Task Should_Add_Strings()
+ {
+ // Given
+ var console = new TestConsole();
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.AddColumn("Column #2");
+ table.AddRow("1", "1-2");
+ table.AddRow("2", "2-2");
+ table.AddRow("3", "3-2");
+
+ // When
+ console.Write(table);
+
+ // Then
+ return Verifier.Verify(console.Output);
+ }
+ }
+
+ [UsesVerify]
+ public sealed class TheInsertRowMethod
+ {
+ [Fact]
+ [Expectation("Insert", "Renderables")]
+ public Task Should_Insert_Renderables()
+ {
+ // Given
+ var console = new TestConsole();
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.AddColumn("Column #2");
+ table.AddRow(new[] { new Text("1"), new Text("1-2") });
+ table.AddRow(new[] { new Text("2"), new Text("2-2") });
+
+ // When
+ table.InsertRow(1, new[] { new Text("3"), new Text("3-2") });
+
+ // Then
+ console.Write(table);
+ return Verifier.Verify(console.Output);
+ }
+
+ [Fact]
+ [Expectation("Insert", "Strings")]
+ public Task Should_Insert_Strings()
+ {
+ // Given
+ var console = new TestConsole();
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.AddColumn("Column #2");
+ table.AddRow("1", "1-2");
+ table.AddRow("2", "2-2");
+
+ // When
+ table.InsertRow(1, "3", "3-2");
+
+
+ // Then
+ console.Write(table);
+ return Verifier.Verify(console.Output);
+ }
+ }
+
+ [UsesVerify]
+ public sealed class TheRemoveRowMethod
+ {
+ [Fact]
+ [Expectation("Remove")]
+ public Task Should_Remove_Row()
+ {
+ // Given
+ var console = new TestConsole();
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.AddColumn("Column #2");
+ table.AddRow(new[] { new Text("1"), new Text("1-2") });
+ table.AddRow(new[] { new Text("2"), new Text("2-2") });
+ table.AddRow(new[] { new Text("3"), new Text("3-2") });
+
+ // When
+ table.RemoveRow(1);
+
+ // Then
+ console.Write(table);
+ return Verifier.Verify(console.Output);
+ }
+ }
+ }
+}
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/Table/TableRowCollectionTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/Table/TableRowCollectionTests.cs
new file mode 100644
index 0000000..15f931f
--- /dev/null
+++ b/test/Spectre.Console.Tests/Unit/Widgets/Table/TableRowCollectionTests.cs
@@ -0,0 +1,226 @@
+using System;
+using System.Threading.Tasks;
+using Shouldly;
+using Spectre.Console.Testing;
+using Spectre.Verify.Extensions;
+using VerifyXunit;
+using Xunit;
+
+namespace Spectre.Console.Tests.Unit
+{
+ [ExpectationPath("Widgets/Table/Rows")]
+ public sealed class TableRowCollectionTests
+ {
+ [UsesVerify]
+ public sealed class TheAddMethod
+ {
+ [Fact]
+ public void Should_Throw_If_Columns_Are_Null()
+ {
+ // Given
+ var table = new Table();
+
+ // When
+ var result = Record.Exception(() => table.Rows.Add(null));
+
+ // Then
+ result.ShouldBeOfType()
+ .ParamName.ShouldBe("columns");
+ }
+
+ [Fact]
+ public void Should_Add_Row_To_Collection()
+ {
+ // Given
+ var table = new Table();
+ table.AddColumn("Column #1");
+
+ // When
+ table.Rows.Add(new[] { Text.Empty });
+
+ // Then
+ table.Rows.Count.ShouldBe(1);
+ }
+
+ [Fact]
+ public void Should_Return_Index_Of_Added_Row()
+ {
+ // Given
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.Rows.Add(new[] { Text.Empty });
+
+ // When
+ var result = table.Rows.Add(new[] { Text.Empty });
+
+ // Then
+ result.ShouldBe(1);
+ }
+
+ [Fact]
+ [Expectation("Add")]
+ public Task Should_Add_Item_At_Correct_Place()
+ {
+ // Given
+ var console = new TestConsole();
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.Rows.Add(new[] { new Text("1") });
+ table.Rows.Add(new[] { new Text("2") });
+ table.Rows.Add(new[] { new Text("3") });
+
+ // When
+ console.Write(table);
+
+ // Then
+ return Verifier.Verify(console.Output);
+ }
+ }
+
+ [UsesVerify]
+ public sealed class TheInsertMethod
+ {
+ [Fact]
+ public void Should_Throw_If_Columns_Are_Null()
+ {
+ // Given
+ var table = new Table();
+
+ // When
+ var result = Record.Exception(() => table.Rows.Insert(0, null));
+
+ // Then
+ result.ShouldBeOfType()
+ .ParamName.ShouldBe("columns");
+ }
+
+ [Fact]
+ public void Should_Insert_Row()
+ {
+ // Given
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.Rows.Add(new[] { Text.Empty });
+
+ // When
+ table.Rows.Insert(0, new[] { Text.Empty });
+
+ // Then
+ table.Rows.Count.ShouldBe(2);
+ }
+
+ [Fact]
+ public void Should_Return_Index_Of_Inserted_Row()
+ {
+ // Given
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.Rows.Add(new[] { new Text("1") });
+ table.Rows.Add(new[] { new Text("2") });
+
+ // When
+ var result = table.Rows.Insert(1, new[] { new Text("3") });
+
+ // Then
+ result.ShouldBe(1);
+ }
+
+ [Fact]
+ [Expectation("Insert")]
+ public Task Should_Insert_Item_At_Correct_Place()
+ {
+ // Given
+ var console = new TestConsole();
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.Rows.Add(new[] { new Text("1") });
+ table.Rows.Add(new[] { new Text("2") });
+ table.Rows.Insert(1, new[] { new Text("3") });
+
+ // When
+ console.Write(table);
+
+ // Then
+ return Verifier.Verify(console.Output);
+ }
+ }
+
+ [UsesVerify]
+ public sealed class TheRemoveMethod
+ {
+ [Fact]
+ public void Should_Throw_If_Index_Is_Negative()
+ {
+ // Given
+ var table = new Table();
+ table.AddColumn("Column #1");
+
+ // When
+ var result = Record.Exception(() => table.Rows.RemoveAt(-1));
+
+ // Then
+ result.ShouldBeOfType()
+ .Message.ShouldBe("Table row index cannot be negative.");
+ }
+
+ [Fact]
+ public void Should_Throw_If_Index_Is_Larger_Than_Number_Of_Rows()
+ {
+ // Given
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.Rows.Add(new[] { new Text("1") });
+ table.Rows.Add(new[] { new Text("2") });
+ table.Rows.Add(new[] { new Text("3") });
+
+ // When
+ var result = Record.Exception(() => table.Rows.RemoveAt(3));
+
+ // Then
+ result.ShouldBeOfType()
+ .Message.ShouldBe("Table row index cannot exceed the number of rows in the table.");
+ }
+
+ [Fact]
+ [Expectation("Remove")]
+ public Task Should_Remove_Row()
+ {
+ // Given
+ var console = new TestConsole();
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.Rows.Add(new[] { new Text("1") });
+ table.Rows.Add(new[] { new Text("2") });
+ table.Rows.Add(new[] { new Text("3") });
+ table.Rows.RemoveAt(1);
+
+ // When
+ console.Write(table);
+
+ // Then
+ return Verifier.Verify(console.Output);
+ }
+ }
+
+ public sealed class TheClearMethod
+ {
+ [Fact]
+ public void Should_Remove_All_Rows()
+ {
+ // Given
+ var table = new Table();
+ table.AddColumn("Column #1");
+ table.Rows.Add(new[] { new Text("1") });
+ table.Rows.Add(new[] { new Text("2") });
+ table.Rows.Add(new[] { new Text("3") });
+ table.Rows.Clear();
+
+ // When
+ var result = table.Rows.Count;
+
+ // Then
+ result.ShouldBe(0);
+ }
+ }
+ }
+}
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/TableTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs
similarity index 97%
rename from test/Spectre.Console.Tests/Unit/Widgets/TableTests.cs
rename to test/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs
index e222124..906169e 100644
--- a/test/Spectre.Console.Tests/Unit/Widgets/TableTests.cs
+++ b/test/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs
@@ -78,20 +78,6 @@ namespace Spectre.Console.Tests.Unit
.ParamName.ShouldBe("columns");
}
- [Fact]
- public void Should_Throw_If_Renderable_Rows_Are_Null()
- {
- // Given
- var table = new Table();
-
- // When
- var result = Record.Exception(() => table.AddRow(null));
-
- // Then
- result.ShouldBeOfType()
- .ParamName.ShouldBe("columns");
- }
-
[Fact]
public void Should_Add_Empty_Items_If_User_Provides_Less_Row_Items_Than_Columns()
{
diff --git a/test/Spectre.Console.Tests/Utilities/GitHubIssueAttribute.cs b/test/Spectre.Console.Tests/Utilities/GitHubIssueAttribute.cs
index 659c9c7..4dbc7d9 100644
--- a/test/Spectre.Console.Tests/Utilities/GitHubIssueAttribute.cs
+++ b/test/Spectre.Console.Tests/Utilities/GitHubIssueAttribute.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Spectre.Console.Tests
{