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 {