Moved analyzer tests to its own project

Also moves tests to `./test` which makes it possible
for all test projects to share the same .editorconfig
files and similar.
This commit is contained in:
Patrik Svensson
2021-06-23 22:47:12 +02:00
parent 721d73e9eb
commit c9b178ac96
307 changed files with 114 additions and 84 deletions

View File

@@ -0,0 +1,61 @@
using Shouldly;
using Spectre.Console.Advanced;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed partial class AnsiConsoleTests
{
public sealed class Advanced
{
[Fact]
public void Should_Write_Ansi_Codes_To_Console_If_Supported()
{
// Given
var console = new TestConsole()
.SupportsAnsi(true)
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.WriteAnsi("Hello");
// Then
console.Output.NormalizeLineEndings()
.ShouldBe("Hello");
}
[Fact]
public void Should_Not_Write_Ansi_Codes_To_Console_If_Not_Supported()
{
// Given
var console = new TestConsole()
.SupportsAnsi(false)
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.WriteAnsi("Hello");
// Then
console.Output.NormalizeLineEndings()
.ShouldBeEmpty();
}
[Fact]
public void Should_Return_Ansi_For_Renderable()
{
// Given
var console = new TestConsole().Colors(ColorSystem.TrueColor);
var markup = new Console.Markup("[yellow]Hello [blue]World[/]![/]");
// When
var result = console.ToAnsi(markup);
// Then
result.ShouldBe("Hello World!");
}
}
}
}

View File

@@ -0,0 +1,248 @@
using System.IO;
using Shouldly;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public partial class AnsiConsoleTests
{
[Theory]
[InlineData(ColorSystemSupport.NoColors, ColorSystem.NoColors)]
[InlineData(ColorSystemSupport.Legacy, ColorSystem.Legacy)]
[InlineData(ColorSystemSupport.Standard, ColorSystem.Standard)]
[InlineData(ColorSystemSupport.EightBit, ColorSystem.EightBit)]
[InlineData(ColorSystemSupport.TrueColor, ColorSystem.TrueColor)]
public void Should_Create_Console_With_Requested_ColorSystem(ColorSystemSupport requested, ColorSystem expected)
{
// Given, When
var console = AnsiConsole.Create(new AnsiConsoleSettings
{
ColorSystem = requested,
Out = new AnsiConsoleOutput(new StringWriter()),
});
// Then
console.Profile.Capabilities.ColorSystem.ShouldBe(expected);
}
public sealed class TrueColor
{
[Theory]
[InlineData(true, "\u001b[38;2;128;0;128mHello\u001b[0m")]
[InlineData(false, "\u001b[48;2;128;0;128mHello\u001b[0m")]
public void Should_Return_Correct_Code(bool foreground, string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.TrueColor)
.EmitAnsiSequences();
// When
console.Write("Hello", new Style().SetColor(new Color(128, 0, 128), foreground));
// Then
console.Output.ShouldBe(expected);
}
[Theory]
[InlineData(true, "\u001b[38;5;5mHello\u001b[0m")]
[InlineData(false, "\u001b[48;5;5mHello\u001b[0m")]
public void Should_Return_Eight_Bit_Ansi_Code_For_Known_Colors(bool foreground, string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.TrueColor)
.EmitAnsiSequences();
// When
console.Write("Hello", new Style().SetColor(Color.Purple, foreground));
// Then
console.Output.ShouldBe(expected);
}
}
public sealed class EightBit
{
[Theory]
[InlineData(true, "\u001b[38;5;3mHello\u001b[0m")]
[InlineData(false, "\u001b[48;5;3mHello\u001b[0m")]
public void Should_Return_Correct_Code_For_Known_Color(bool foreground, string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.EightBit)
.EmitAnsiSequences();
// When
console.Write("Hello", new Style().SetColor(Color.Olive, foreground));
// Then
console.Output.ShouldBe(expected);
}
[Theory]
[InlineData(true, "\u001b[38;5;3mHello\u001b[0m")]
[InlineData(false, "\u001b[48;5;3mHello\u001b[0m")]
public void Should_Map_TrueColor_To_Nearest_Eight_Bit_Color_If_Possible(bool foreground, string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.EightBit)
.EmitAnsiSequences();
// When
console.Write("Hello", new Style().SetColor(new Color(128, 128, 0), foreground));
// Then
console.Output.ShouldBe(expected);
}
[Theory]
[InlineData(true, "\u001b[38;5;3mHello\u001b[0m")]
[InlineData(false, "\u001b[48;5;3mHello\u001b[0m")]
public void Should_Estimate_TrueColor_To_Nearest_Eight_Bit_Color(bool foreground, string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.EightBit)
.EmitAnsiSequences();
// When
console.Write("Hello", new Style().SetColor(new Color(126, 127, 0), foreground));
// Then
console.Output.ShouldBe(expected);
}
}
public sealed class Standard
{
[Theory]
[InlineData(true, "\u001b[33mHello\u001b[0m")]
[InlineData(false, "\u001b[43mHello\u001b[0m")]
public void Should_Return_Correct_Code_For_Known_Color(bool foreground, string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.Write("Hello", new Style().SetColor(Color.Olive, foreground));
// Then
console.Output.ShouldBe(expected);
}
[Theory]
[InlineData(true, 128, 128, 128, "\u001b[90mHello\u001b[0m")]
[InlineData(false, 128, 128, 128, "\u001b[100mHello\u001b[0m")]
[InlineData(true, 0, 128, 0, "\u001b[32mHello\u001b[0m")]
[InlineData(false, 0, 128, 0, "\u001b[42mHello\u001b[0m")]
public void Should_Map_TrueColor_To_Nearest_Four_Bit_Color_If_Possible(
bool foreground,
byte r, byte g, byte b,
string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground));
// Then
console.Output.ShouldBe(expected);
}
[Theory]
[InlineData(true, 112, 120, 128, "\u001b[90mHello\u001b[0m")]
[InlineData(false, 112, 120, 128, "\u001b[100mHello\u001b[0m")]
[InlineData(true, 0, 120, 12, "\u001b[32mHello\u001b[0m")]
[InlineData(false, 0, 120, 12, "\u001b[42mHello\u001b[0m")]
public void Should_Estimate_TrueColor_To_Nearest_Four_Bit_Color(
bool foreground,
byte r, byte g, byte b,
string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground));
// Then
console.Output.ShouldBe(expected);
}
}
public sealed class Legacy
{
[Theory]
[InlineData(true, "\u001b[33mHello\u001b[0m")]
[InlineData(false, "\u001b[43mHello\u001b[0m")]
public void Should_Return_Correct_Code_For_Known_Color(bool foreground, string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Legacy)
.EmitAnsiSequences();
// When
console.Write("Hello", new Style().SetColor(Color.Olive, foreground));
// Then
console.Output.ShouldBe(expected);
}
[Theory]
[InlineData(true, 128, 128, 128, "\u001b[37mHello\u001b[0m")]
[InlineData(false, 128, 128, 128, "\u001b[47mHello\u001b[0m")]
[InlineData(true, 0, 128, 0, "\u001b[32mHello\u001b[0m")]
[InlineData(false, 0, 128, 0, "\u001b[42mHello\u001b[0m")]
public void Should_Map_TrueColor_To_Nearest_Three_Bit_Color_If_Possible(
bool foreground,
byte r, byte g, byte b,
string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Legacy)
.EmitAnsiSequences();
// When
console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground));
// Then
console.Output.ShouldBe(expected);
}
[Theory]
[InlineData(true, 112, 120, 128, "\u001b[36mHello\u001b[0m")]
[InlineData(false, 112, 120, 128, "\u001b[46mHello\u001b[0m")]
[InlineData(true, 0, 120, 12, "\u001b[32mHello\u001b[0m")]
[InlineData(false, 0, 120, 12, "\u001b[42mHello\u001b[0m")]
public void Should_Estimate_TrueColor_To_Nearest_Three_Bit_Color(
bool foreground,
byte r, byte g, byte b,
string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Legacy)
.EmitAnsiSequences();
// When
console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground));
// Then
console.Output.ShouldBe(expected);
}
}
}
}

View File

@@ -0,0 +1,52 @@
using Shouldly;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public partial class AnsiConsoleTests
{
public sealed class Cursor
{
public sealed class TheMoveMethod
{
[Theory]
[InlineData(CursorDirection.Up, "HelloWorld")]
[InlineData(CursorDirection.Down, "HelloWorld")]
[InlineData(CursorDirection.Right, "HelloWorld")]
[InlineData(CursorDirection.Left, "HelloWorld")]
public void Should_Return_Correct_Ansi_Code(CursorDirection direction, string expected)
{
// Given
var console = new TestConsole().EmitAnsiSequences();
// When
console.Write("Hello");
console.Cursor.Move(direction, 2);
console.Write("World");
// Then
console.Output.ShouldBe(expected);
}
}
public sealed class TheSetPositionMethod
{
[Fact]
public void Should_Return_Correct_Ansi_Code()
{
// Given
var console = new TestConsole().EmitAnsiSequences();
// When
console.Write("Hello");
console.Cursor.SetPosition(5, 3);
console.Write("World");
// Then
console.Output.ShouldBe("HelloWorld");
}
}
}
}
}

View File

@@ -0,0 +1,120 @@
using System;
using Shouldly;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public partial class AnsiConsoleTests
{
public sealed class Markup
{
[Theory]
[InlineData("[yellow]Hello[/]", "Hello")]
[InlineData("[yellow]Hello [italic]World[/]![/]", "Hello World!")]
public void Should_Output_Expected_Ansi_For_Markup(string markup, string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.Markup(markup);
// Then
console.Output.ShouldBe(expected);
}
[Fact]
public void Should_Output_Expected_Ansi_For_Link_With_Url_And_Text()
{
// Given
var console = new TestConsole()
.EmitAnsiSequences();
// When
console.Markup("[link=https://patriksvensson.se]Click to visit my blog[/]");
// Then
console.Output.ShouldMatch("]8;id=[0-9]*;https:\\/\\/patriksvensson\\.se\\\\Click to visit my blog]8;;\\\\");
}
[Fact]
public void Should_Output_Expected_Ansi_For_Link_With_Only_Url()
{
// Given
var console = new TestConsole()
.EmitAnsiSequences();
// When
console.Markup("[link]https://patriksvensson.se[/]");
// Then
console.Output.ShouldMatch("]8;id=[0-9]*;https:\\/\\/patriksvensson\\.se\\\\https:\\/\\/patriksvensson\\.se]8;;\\\\");
}
[Theory]
[InlineData("[yellow]Hello [[ World[/]", "Hello [ World")]
public void Should_Be_Able_To_Escape_Tags(string markup, string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.Markup(markup);
// Then
console.Output.ShouldBe(expected);
}
[Theory]
[InlineData("[yellow]Hello[", "Encountered malformed markup tag at position 14.")]
[InlineData("[yellow]Hello[/", "Encountered malformed markup tag at position 15.")]
[InlineData("[yellow]Hello[/foo", "Encountered malformed markup tag at position 15.")]
[InlineData("[yellow Hello", "Encountered malformed markup tag at position 13.")]
public void Should_Throw_If_Encounters_Malformed_Tag(string markup, string expected)
{
// Given
var console = new TestConsole();
// When
var result = Record.Exception(() => console.Markup(markup));
// Then
result.ShouldBeOfType<InvalidOperationException>()
.Message.ShouldBe(expected);
}
[Fact]
public void Should_Throw_If_Tags_Are_Unbalanced()
{
// Given
var console = new TestConsole();
// When
var result = Record.Exception(() => console.Markup("[yellow][blue]Hello[/]"));
// Then
result.ShouldBeOfType<InvalidOperationException>()
.Message.ShouldBe("Unbalanced markup stack. Did you forget to close a tag?");
}
[Fact]
public void Should_Throw_If_Encounters_Closing_Tag()
{
// Given
var console = new TestConsole();
// When
var result = Record.Exception(() => console.Markup("Hello[/]World"));
// Then
result.ShouldBeOfType<InvalidOperationException>()
.Message.ShouldBe("Encountered closing tag when none was expected near position 5.");
}
}
}
}

View File

@@ -0,0 +1,48 @@
using Shouldly;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public partial class AnsiConsoleTests
{
[Theory]
[InlineData(Decoration.Bold, "\u001b[1mHello World")]
[InlineData(Decoration.Dim, "\u001b[2mHello World")]
[InlineData(Decoration.Italic, "\u001b[3mHello World")]
[InlineData(Decoration.Underline, "\u001b[4mHello World")]
[InlineData(Decoration.Invert, "\u001b[7mHello World")]
[InlineData(Decoration.Conceal, "\u001b[8mHello World")]
[InlineData(Decoration.SlowBlink, "\u001b[5mHello World")]
[InlineData(Decoration.RapidBlink, "\u001b[6mHello World")]
[InlineData(Decoration.Strikethrough, "\u001b[9mHello World")]
public void Should_Write_Decorated_Text_Correctly(Decoration decoration, string expected)
{
// Given
var console = new TestConsole()
.EmitAnsiSequences();
// When
console.Write("Hello World", new Style().Decoration(decoration));
// Then
console.Output.ShouldBe(expected);
}
[Theory]
[InlineData(Decoration.Bold | Decoration.Underline, "\u001b[1;4mHello World")]
[InlineData(Decoration.Bold | Decoration.Underline | Decoration.Conceal, "\u001b[1;4;8mHello World")]
public void Should_Write_Text_With_Multiple_Decorations_Correctly(Decoration decoration, string expected)
{
// Given
var console = new TestConsole()
.EmitAnsiSequences();
// When
console.Write("Hello World", new Style().Decoration(decoration));
// Then
console.Output.ShouldBe(expected);
}
}
}

View File

@@ -0,0 +1,151 @@
using System;
using Shouldly;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public partial class AnsiConsoleTests
{
public sealed class Clear
{
[Theory]
[InlineData(false, "HelloWorld")]
[InlineData(true, "HelloWorld")]
public void Should_Clear_Screen(bool home, string expected)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.Write("Hello");
console.Clear(home);
console.Write("World");
// Then
console.Output.ShouldBe(expected);
}
}
public sealed class Write
{
[Fact]
public void Should_Combine_Decoration_And_Colors()
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.Write(
"Hello",
new Style()
.Foreground(Color.RoyalBlue1)
.Background(Color.NavajoWhite1)
.Decoration(Decoration.Italic));
// Then
console.Output.ShouldBe("\u001b[3;90;47mHello\u001b[0m");
}
[Fact]
public void Should_Not_Include_Foreground_If_Set_To_Default_Color()
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.Write(
"Hello",
new Style()
.Foreground(Color.Default)
.Background(Color.NavajoWhite1)
.Decoration(Decoration.Italic));
// Then
console.Output.ShouldBe("\u001b[3;47mHello\u001b[0m");
}
[Fact]
public void Should_Not_Include_Background_If_Set_To_Default_Color()
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.Write(
"Hello",
new Style()
.Foreground(Color.RoyalBlue1)
.Background(Color.Default)
.Decoration(Decoration.Italic));
// Then
console.Output.ShouldBe("\u001b[3;90mHello\u001b[0m");
}
[Fact]
public void Should_Not_Include_Decoration_If_Set_To_None()
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.Write(
"Hello",
new Style()
.Foreground(Color.RoyalBlue1)
.Background(Color.NavajoWhite1)
.Decoration(Decoration.None));
// Then
console.Output.ShouldBe("\u001b[90;47mHello\u001b[0m");
}
}
public sealed class WriteLine
{
[Fact]
public void Should_Reset_Colors_Correctly_After_Line_Break()
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.WriteLine("Hello", new Style().Background(ConsoleColor.Red));
console.WriteLine("World", new Style().Background(ConsoleColor.Green));
// Then
console.Output.NormalizeLineEndings()
.ShouldBe("Hello\nWorld\n");
}
[Fact]
public void Should_Reset_Colors_Correctly_After_Line_Break_In_Text()
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
// When
console.WriteLine("Hello\nWorld", new Style().Background(ConsoleColor.Red));
// Then
console.Output.NormalizeLineEndings()
.ShouldBe("Hello\nWorld\n");
}
}
}
}

View File

@@ -0,0 +1,51 @@
using System.Threading.Tasks;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/BarChart")]
public sealed class BarChartTests
{
[Fact]
[Expectation("Render")]
public async Task Should_Render_Correctly()
{
// Given
var console = new TestConsole();
// When
console.Write(new BarChart()
.Width(60)
.Label("Number of fruits")
.AddItem("Apple", 12)
.AddItem("Orange", 54)
.AddItem("Banana", 33));
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Zero_Value")]
public async Task Should_Render_Correctly_2()
{
// Given
var console = new TestConsole();
// When
console.Write(new BarChart()
.Width(60)
.Label("Number of fruits")
.AddItem("Apple", 0)
.AddItem("Orange", 54)
.AddItem("Banana", 33));
// Then
await Verifier.Verify(console.Output);
}
}
}

View File

@@ -0,0 +1,210 @@
using System.Threading.Tasks;
using Shouldly;
using Spectre.Console.Rendering;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Borders/Box")]
public sealed class BoxBorderTests
{
[UsesVerify]
public sealed class NoBorder
{
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = BoxExtensions.GetSafeBorder(BoxBorder.None, safe: true);
// Then
border.ShouldBeSameAs(BoxBorder.None);
}
}
[Fact]
[Expectation("NoBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var panel = Fixture.GetPanel().NoBorder();
// When
console.Write(panel);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class AsciiBorder
{
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = BoxExtensions.GetSafeBorder(BoxBorder.Ascii, safe: true);
// Then
border.ShouldBeSameAs(BoxBorder.Ascii);
}
}
[Fact]
[Expectation("AsciiBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var panel = Fixture.GetPanel().AsciiBorder();
// When
console.Write(panel);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class DoubleBorder
{
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = BoxExtensions.GetSafeBorder(BoxBorder.Double, safe: true);
// Then
border.ShouldBeSameAs(BoxBorder.Double);
}
}
[Fact]
[Expectation("DoubleBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var panel = Fixture.GetPanel().DoubleBorder();
// When
console.Write(panel);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class HeavyBorder
{
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = BoxExtensions.GetSafeBorder(BoxBorder.Heavy, safe: true);
// Then
border.ShouldBeSameAs(BoxBorder.Square);
}
}
[Fact]
[Expectation("HeavyBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var panel = Fixture.GetPanel().HeavyBorder();
// When
console.Write(panel);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class RoundedBorder
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = BoxExtensions.GetSafeBorder(BoxBorder.Rounded, safe: true);
// Then
border.ShouldBeSameAs(BoxBorder.Square);
}
[Fact]
[Expectation("RoundedBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var panel = Fixture.GetPanel().RoundedBorder();
// When
console.Write(panel);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class SquareBorder
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = BoxExtensions.GetSafeBorder(BoxBorder.Square, safe: true);
// Then
border.ShouldBeSameAs(BoxBorder.Square);
}
[Fact]
[Expectation("SquareBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var panel = Fixture.GetPanel().SquareBorder();
// When
console.Write(panel);
// Then
return Verifier.Verify(console.Output);
}
}
private static class Fixture
{
public static Panel GetPanel()
{
return new Panel("Hello World")
.Header("Greeting");
}
}
}
}

View File

@@ -0,0 +1,150 @@
using System.Threading.Tasks;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/BreakdownChart")]
public sealed class BreakdownChartTests
{
[Fact]
[Expectation("Default")]
public async Task Should_Render_Correctly()
{
// Given
var console = new TestConsole();
var chart = Fixture.GetChart();
// When
console.Write(chart);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Width")]
public async Task Should_Render_With_Specific_Width()
{
// Given
var console = new TestConsole();
var chart = Fixture.GetChart().Width(60);
// When
console.Write(chart);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("TagFormat")]
public async Task Should_Render_Correctly_With_Specific_Value_Formatter()
{
// Given
var console = new TestConsole();
var chart = Fixture.GetChart()
.Width(60)
.Culture("sv-SE")
.UseValueFormatter((v, c) => string.Format(c, "{0}%", v));
// When
console.Write(chart);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("HideTags")]
public async Task Should_Render_Correctly_Without_Tags()
{
// Given
var console = new TestConsole();
var chart = Fixture.GetChart().Width(60).HideTags();
// When
console.Write(chart);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("HideTagValues")]
public async Task Should_Render_Correctly_Without_Tag_Values()
{
// Given
var console = new TestConsole();
var chart = Fixture.GetChart().Width(60).HideTagValues();
// When
console.Write(chart);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Culture")]
public async Task Should_Render_Correctly_With_Specific_Culture()
{
// Given
var console = new TestConsole();
var chart = Fixture.GetChart().Width(60).Culture("sv-SE");
// When
console.Write(chart);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("FullSize")]
public async Task Should_Render_FullSize_Mode_Correctly()
{
// Given
var console = new TestConsole();
var chart = Fixture.GetChart().Width(60).FullSize();
// When
console.Write(chart);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Ansi")]
public async Task Should_Render_Correct_Ansi()
{
// Given
var console = new TestConsole().EmitAnsiSequences();
var chart = Fixture.GetChart().Width(60).FullSize();
// When
console.Write(chart);
// Then
await Verifier.Verify(console.Output);
}
public static class Fixture
{
public static BreakdownChart GetChart()
{
return new BreakdownChart()
.AddItem("SCSS", 37, Color.Red)
.AddItem("HTML", 28.3, Color.Blue)
.AddItem("C#", 22.6, Color.Green)
.AddItem("JavaScript", 6, Color.Yellow)
.AddItem("Ruby", 6, Color.LightGreen)
.AddItem("Shell", 0.1, Color.Aqua);
}
}
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.Threading.Tasks;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/Calendar")]
public sealed class CalendarTests
{
[Fact]
[Expectation("Render")]
public Task Should_Render_Calendar_Correctly()
{
// Given
var console = new TestConsole();
var calendar = new Calendar(2020, 10)
.AddCalendarEvent(new DateTime(2020, 9, 1))
.AddCalendarEvent(new DateTime(2020, 10, 3))
.AddCalendarEvent(new DateTime(2020, 10, 12));
// When
console.Write(calendar);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Centered")]
public Task Should_Center_Calendar_Correctly()
{
// Given
var console = new TestConsole();
var calendar = new Calendar(2020, 10)
.Centered()
.AddCalendarEvent(new DateTime(2020, 9, 1))
.AddCalendarEvent(new DateTime(2020, 10, 3))
.AddCalendarEvent(new DateTime(2020, 10, 12));
// When
console.Write(calendar);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("LeftAligned")]
public Task Should_Left_Align_Calendar_Correctly()
{
// Given
var console = new TestConsole();
var calendar = new Calendar(2020, 10)
.LeftAligned()
.AddCalendarEvent(new DateTime(2020, 9, 1))
.AddCalendarEvent(new DateTime(2020, 10, 3))
.AddCalendarEvent(new DateTime(2020, 10, 12));
// When
console.Write(calendar);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("RightAligned")]
public Task Should_Right_Align_Calendar_Correctly()
{
// Given
var console = new TestConsole();
var calendar = new Calendar(2020, 10)
.RightAligned()
.AddCalendarEvent(new DateTime(2020, 9, 1))
.AddCalendarEvent(new DateTime(2020, 10, 3))
.AddCalendarEvent(new DateTime(2020, 10, 12));
// When
console.Write(calendar);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Culture")]
public Task Should_Render_Calendar_Correctly_For_Specific_Culture()
{
// Given
var console = new TestConsole();
var calendar = new Calendar(2020, 10, 15)
.Culture("de-DE")
.AddCalendarEvent(new DateTime(2020, 9, 1))
.AddCalendarEvent(new DateTime(2020, 10, 3))
.AddCalendarEvent(new DateTime(2020, 10, 12));
// When
console.Write(calendar);
// Then
return Verifier.Verify(console.Output);
}
}
}

View File

@@ -0,0 +1,143 @@
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
{
[UsesVerify]
[ExpectationPath("Widgets/Canvas")]
public class CanvasTests
{
public sealed class TheConstructor
{
[Fact]
public void Should_Throw_If_Width_Is_Less_Than_Zero()
{
// Given, When
var result = Record.Exception(() => new Canvas(0, 1));
// Then
result.ShouldBeOfType<ArgumentException>()
.And(ex => ex.ParamName.ShouldBe("width"));
}
[Fact]
public void Should_Throw_If_Height_Is_Less_Than_Zero()
{
// Given, When
var result = Record.Exception(() => new Canvas(1, 0));
// Then
result.ShouldBeOfType<ArgumentException>()
.And(ex => ex.ParamName.ShouldBe("height"));
}
}
[Fact]
[Expectation("Render")]
public async Task Should_Render_Canvas_Correctly()
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
var canvas = new Canvas(width: 5, height: 5);
canvas.SetPixel(0, 0, Color.Red);
canvas.SetPixel(4, 0, Color.Green);
canvas.SetPixel(0, 4, Color.Blue);
canvas.SetPixel(4, 4, Color.Yellow);
// When
console.Write(canvas);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Nested")]
public async Task Simple_Measure()
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
var panel = new Panel(new Canvas(width: 2, height: 2)
.SetPixel(0, 0, Color.Aqua)
.SetPixel(1, 1, Color.Grey));
// When
console.Write(panel);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_NarrowTerminal")]
public async Task Should_Scale_Down_Canvas_Is_Bigger_Than_Terminal()
{
// Given
var console = new TestConsole()
.Width(10)
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
var canvas = new Canvas(width: 20, height: 10);
canvas.SetPixel(0, 0, Color.Aqua);
canvas.SetPixel(19, 9, Color.Grey);
// When
console.Write(canvas);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_MaxWidth")]
public async Task Should_Scale_Down_Canvas_If_MaxWidth_Is_Set()
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
var canvas = new Canvas(width: 20, height: 10) { MaxWidth = 10 };
canvas.SetPixel(0, 0, Color.Aqua);
canvas.SetPixel(19, 9, Color.Aqua);
// When
console.Write(canvas);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
public void Should_Not_Render_Canvas_If_Canvas_Cannot_Be_Scaled_Down()
{
// Given
var console = new TestConsole()
.Width(10)
.Colors(ColorSystem.Standard)
.EmitAnsiSequences();
var canvas = new Canvas(width: 20, height: 2);
canvas.SetPixel(0, 0, Color.Aqua);
canvas.SetPixel(19, 1, Color.Grey);
// When
console.Write(canvas);
// Then
console.Output.ShouldBeEmpty();
}
}
}

View File

@@ -0,0 +1,97 @@
using System.Threading.Tasks;
using Spectre.Console.Cli;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli.Annotations
{
[ExpectationPath("Cli/Arguments")]
public sealed partial class CommandArgumentAttributeTests
{
[UsesVerify]
public sealed class ArgumentCannotContainOptions
{
public sealed class Settings : CommandSettings
{
[CommandArgument(0, "--foo <BAR>")]
public string Foo { get; set; }
}
[Fact]
[Expectation("ArgumentCannotContainOptions")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
public sealed class MultipleValuesAreNotSupported
{
public sealed class Settings : CommandSettings
{
[CommandArgument(0, "<FOO> <BAR>")]
public string Foo { get; set; }
}
[Fact]
[Expectation("MultipleValuesAreNotSupported")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
public sealed class ValuesMustHaveName
{
public sealed class Settings : CommandSettings
{
[CommandArgument(0, "<>")]
public string Foo { get; set; }
}
[Fact]
[Expectation("ValuesMustHaveName")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
return Verifier.Verify(result);
}
}
private static class Fixture
{
public static string Run<TSettings>(params string[] args)
where TSettings : CommandSettings
{
using (var writer = new TestConsole())
{
var app = new CommandApp();
app.Configure(c => c.ConfigureConsole(writer));
app.Configure(c => c.AddCommand<GenericCommand<TSettings>>("foo"));
app.Run(args);
return writer.Output
.NormalizeLineEndings()
.TrimLines()
.Trim();
}
}
}
}
}

View File

@@ -0,0 +1,64 @@
using Shouldly;
using Spectre.Console.Cli;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli.Annotations
{
public sealed partial class CommandArgumentAttributeTests
{
[Fact]
public void Should_Not_Contain_Options()
{
// Given, When
var result = Record.Exception(() => new CommandArgumentAttribute(0, "--foo <BAR>"));
// Then
result.ShouldNotBe(null);
result.ShouldBeOfType<CommandTemplateException>().And(exception =>
exception.Message.ShouldBe("Arguments can not contain options."));
}
[Theory]
[InlineData("<FOO> <BAR>")]
[InlineData("[FOO] [BAR]")]
[InlineData("[FOO] <BAR>")]
[InlineData("<FOO> [BAR]")]
public void Should_Not_Contain_Multiple_Value_Names(string template)
{
// Given, When
var result = Record.Exception(() => new CommandArgumentAttribute(0, template));
// Then
result.ShouldNotBe(null);
result.ShouldBeOfType<CommandTemplateException>().And(exception =>
exception.Message.ShouldBe("Multiple values are not supported."));
}
[Theory]
[InlineData("<>")]
[InlineData("[]")]
public void Should_Not_Contain_Empty_Value_Name(string template)
{
// Given, When
var result = Record.Exception(() => new CommandArgumentAttribute(0, template));
// Then
result.ShouldNotBe(null);
result.ShouldBeOfType<CommandTemplateException>().And(exception =>
exception.Message.ShouldBe("Values without name are not allowed."));
}
[Theory]
[InlineData("<FOO>", true)]
[InlineData("[FOO]", false)]
public void Should_Parse_Valid_Options(string template, bool required)
{
// Given, When
var result = new CommandArgumentAttribute(0, template);
// Then
result.ValueName.ShouldBe("FOO");
result.IsRequired.ShouldBe(required);
}
}
}

View File

@@ -0,0 +1,250 @@
using System.Threading.Tasks;
using Shouldly;
using Spectre.Console.Cli;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli.Annotations
{
[ExpectationPath("Cli/Arguments")]
public sealed partial class CommandOptionAttributeTests
{
[UsesVerify]
public sealed class UnexpectedCharacter
{
public sealed class Settings : CommandSettings
{
[CommandOption("<FOO> $ <BAR>")]
public string Foo { get; set; }
}
[Fact]
[Expectation("UnexpectedCharacter")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
result.Exception.Message.ShouldBe("Encountered unexpected character '$'.");
return Verifier.Verify(result.Output);
}
}
[UsesVerify]
public sealed class UnterminatedValueName
{
public sealed class Settings : CommandSettings
{
[CommandOption("--foo|-f <BAR")]
public string Foo { get; set; }
}
[Fact]
[Expectation("UnterminatedValueName")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
result.Exception.Message.ShouldBe("Encountered unterminated value name 'BAR'.");
return Verifier.Verify(result.Output);
}
}
[UsesVerify]
public sealed class OptionsMustHaveName
{
public sealed class Settings : CommandSettings
{
[CommandOption("--foo|-")]
public string Foo { get; set; }
}
[Fact]
[Expectation("OptionsMustHaveName")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
result.Exception.Message.ShouldBe("Options without name are not allowed.");
return Verifier.Verify(result.Output);
}
}
[UsesVerify]
public sealed class OptionNamesCannotStartWithDigit
{
public sealed class Settings : CommandSettings
{
[CommandOption("--1foo")]
public string Foo { get; set; }
}
[Fact]
[Expectation("OptionNamesCannotStartWithDigit")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
result.Exception.Message.ShouldBe("Option names cannot start with a digit.");
return Verifier.Verify(result.Output);
}
}
[UsesVerify]
public sealed class InvalidCharacterInOptionName
{
public sealed class Settings : CommandSettings
{
[CommandOption("--f$oo")]
public string Foo { get; set; }
}
[Fact]
[Expectation("InvalidCharacterInOptionName")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
result.Exception.Message.ShouldBe("Encountered invalid character '$' in option name.");
return Verifier.Verify(result.Output);
}
}
[UsesVerify]
public sealed class LongOptionMustHaveMoreThanOneCharacter
{
public sealed class Settings : CommandSettings
{
[CommandOption("--f")]
public string Foo { get; set; }
}
[Fact]
[Expectation("LongOptionMustHaveMoreThanOneCharacter")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
result.Exception.Message.ShouldBe("Long option names must consist of more than one character.");
return Verifier.Verify(result.Output);
}
}
[UsesVerify]
public sealed class ShortOptionMustOnlyBeOneCharacter
{
public sealed class Settings : CommandSettings
{
[CommandOption("--foo|-bar")]
public string Foo { get; set; }
}
[Fact]
[Expectation("ShortOptionMustOnlyBeOneCharacter")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
result.Exception.Message.ShouldBe("Short option names can not be longer than one character.");
return Verifier.Verify(result.Output);
}
}
[UsesVerify]
public sealed class MultipleOptionValuesAreNotSupported
{
public sealed class Settings : CommandSettings
{
[CommandOption("-f|--foo <FOO> <BAR>")]
public string Foo { get; set; }
}
[Fact]
[Expectation("MultipleOptionValuesAreNotSupported")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
result.Exception.Message.ShouldBe("Multiple option values are not supported.");
return Verifier.Verify(result.Output);
}
}
[UsesVerify]
public sealed class InvalidCharacterInValueName
{
public sealed class Settings : CommandSettings
{
[CommandOption("-f|--foo <F$OO>")]
public string Foo { get; set; }
}
[Fact]
[Expectation("InvalidCharacterInValueName")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
result.Exception.Message.ShouldBe("Encountered invalid character '$' in value name.");
return Verifier.Verify(result.Output);
}
}
[UsesVerify]
public sealed class MissingLongAndShortName
{
public sealed class Settings : CommandSettings
{
[CommandOption("<FOO>")]
public string Foo { get; set; }
}
[Fact]
[Expectation("MissingLongAndShortName")]
public Task Should_Return_Correct_Text()
{
// Given, When
var result = Fixture.Run<Settings>();
// Then
result.Exception.Message.ShouldBe("No long or short name for option has been specified.");
return Verifier.Verify(result.Output);
}
}
private static class Fixture
{
public static CommandAppFailure Run<TSettings>(params string[] args)
where TSettings : CommandSettings
{
var app = new CommandAppTester();
app.Configure(c =>
{
c.AddCommand<GenericCommand<TSettings>>("foo");
});
return app.RunAndCatch<CommandTemplateException>(args);
}
}
}
}

View File

@@ -0,0 +1,197 @@
using Shouldly;
using Spectre.Console.Cli;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli.Annotations
{
public sealed partial class CommandOptionAttributeTests
{
[Fact]
public void Should_Parse_Short_Name_Correctly()
{
// Given, When
var option = new CommandOptionAttribute("-o|--option <VALUE>");
// Then
option.ShortNames.ShouldContain("o");
}
[Fact]
public void Should_Parse_Long_Name_Correctly()
{
// Given, When
var option = new CommandOptionAttribute("-o|--option <VALUE>");
// Then
option.LongNames.ShouldContain("option");
}
[Theory]
[InlineData("<VALUE>")]
public void Should_Parse_Value_Correctly(string value)
{
// Given, When
var option = new CommandOptionAttribute($"-o|--option {value}");
// Then
option.ValueName.ShouldBe("VALUE");
}
[Fact]
public void Should_Parse_Only_Short_Name()
{
// Given, When
var option = new CommandOptionAttribute("-o");
// Then
option.ShortNames.ShouldContain("o");
}
[Fact]
public void Should_Parse_Only_Long_Name()
{
// Given, When
var option = new CommandOptionAttribute("--option");
// Then
option.LongNames.ShouldContain("option");
}
[Theory]
[InlineData("")]
[InlineData("<VALUE>")]
public void Should_Throw_If_Template_Is_Empty(string value)
{
// Given, When
var option = Record.Exception(() => new CommandOptionAttribute(value));
// Then
option.ShouldBeOfType<CommandTemplateException>().And(e =>
e.Message.ShouldBe("No long or short name for option has been specified."));
}
[Theory]
[InlineData("--bar|-foo")]
[InlineData("--bar|-f-b")]
public void Should_Throw_If_Short_Name_Is_Invalid(string value)
{
// Given, When
var option = Record.Exception(() => new CommandOptionAttribute(value));
// Then
option.ShouldBeOfType<CommandTemplateException>().And(e =>
e.Message.ShouldBe("Short option names can not be longer than one character."));
}
[Theory]
[InlineData("--o")]
public void Should_Throw_If_Long_Name_Is_Invalid(string value)
{
// Given, When
var option = Record.Exception(() => new CommandOptionAttribute(value));
// Then
option.ShouldBeOfType<CommandTemplateException>().And(e =>
e.Message.ShouldBe("Long option names must consist of more than one character."));
}
[Theory]
[InlineData("-")]
[InlineData("--")]
public void Should_Throw_If_Option_Have_No_Name(string template)
{
// Given, When
var option = Record.Exception(() => new CommandOptionAttribute(template));
// Then
option.ShouldBeOfType<CommandTemplateException>().And(e =>
e.Message.ShouldBe("Options without name are not allowed."));
}
[Theory]
[InlineData("--foo|-foo[b", '[')]
[InlineData("--foo|-f€b", '€')]
[InlineData("--foo|-foo@b", '@')]
public void Should_Throw_If_Option_Contains_Invalid_Name(string template, char invalid)
{
// Given, When
var result = Record.Exception(() => new CommandOptionAttribute(template));
// Then
result.ShouldBeOfType<CommandTemplateException>().And(e =>
{
e.Message.ShouldBe($"Encountered invalid character '{invalid}' in option name.");
e.Template.ShouldBe(template);
});
}
[Theory]
[InlineData("--foo <HELLO-WORLD>", "HELLO-WORLD")]
[InlineData("--foo <HELLO_WORLD>", "HELLO_WORLD")]
public void Should_Accept_Dash_And_Underscore_In_Value_Name(string template, string name)
{
// Given, When
var result = new CommandOptionAttribute(template);
// Then
result.ValueName.ShouldBe(name);
}
[Theory]
[InlineData("--foo|-1")]
public void Should_Throw_If_First_Letter_Of_An_Option_Name_Is_A_Digit(string template)
{
// Given, When
var result = Record.Exception(() => new CommandOptionAttribute(template));
// Then
result.ShouldBeOfType<CommandTemplateException>().And(e =>
{
e.Message.ShouldBe("Option names cannot start with a digit.");
e.Template.ShouldBe(template);
});
}
[Fact]
public void Multiple_Short_Options_Are_Supported()
{
// Given, When
var result = new CommandOptionAttribute("-f|-b");
// Then
result.ShortNames.Count.ShouldBe(2);
result.ShortNames.ShouldContain("f");
result.ShortNames.ShouldContain("b");
}
[Fact]
public void Multiple_Long_Options_Are_Supported()
{
// Given, When
var result = new CommandOptionAttribute("--foo|--bar");
// Then
result.LongNames.Count.ShouldBe(2);
result.LongNames.ShouldContain("foo");
result.LongNames.ShouldContain("bar");
}
[Theory]
[InlineData("-f|--foo <BAR>")]
[InlineData("--foo|-f <BAR>")]
[InlineData("<BAR> --foo|-f")]
[InlineData("<BAR> -f|--foo")]
[InlineData("-f <BAR> --foo")]
[InlineData("--foo <BAR> -f")]
public void Template_Parts_Can_Appear_In_Any_Order(string template)
{
// Given, When
var result = new CommandOptionAttribute(template);
// Then
result.LongNames.ShouldContain("foo");
result.ShortNames.ShouldContain("f");
result.ValueName.ShouldBe("BAR");
}
}
}

View File

@@ -0,0 +1,110 @@
using Shouldly;
using Spectre.Console.Cli;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public class NullableSettings : CommandSettings
{
public NullableSettings(bool? detailed, string[] extra)
{
Detailed = detailed;
Extra = extra;
}
[CommandOption("-d")]
public bool? Detailed { get; }
[CommandArgument(0, "[extra]")]
public string[] Extra { get; }
}
public class NullableWithInitSettings : CommandSettings
{
[CommandOption("-d")]
public bool? Detailed { get; init; }
[CommandArgument(0, "[extra]")]
public string[] Extra { get; init; }
}
public class NullableCommand : Command<NullableSettings>
{
public override int Execute(CommandContext context, NullableSettings settings) => 0;
}
public class NullableWithInitCommand : Command<NullableWithInitSettings>
{
public override int Execute(CommandContext context, NullableWithInitSettings settings) => 0;
}
[Fact]
public void Should_Populate_Nullable_Objects_In_Settings()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddCommand<NullableCommand>("null");
});
// When
var result = fixture.Run("null");
// Then
result.Settings.ShouldBeOfType<NullableSettings>().And(settings =>
{
settings.Detailed.ShouldBeNull();
settings.Extra.ShouldBeNull();
});
}
[Fact]
public void Should_Populate_Nullable_Objects_With_Init_In_Settings()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddCommand<NullableWithInitCommand>("null");
});
// When
var result = fixture.Run("null");
// Then
result.Settings.ShouldBeOfType<NullableWithInitSettings>().And(settings =>
{
settings.Detailed.ShouldBeNull();
settings.Extra.ShouldBeNull();
});
}
[Fact]
public void Should_Populate_Regular_Settings()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddCommand<NullableCommand>("null");
});
// When
var result = fixture.Run("null", "-d", "true", "first-item");
// Then
result.Settings.ShouldBeOfType<NullableSettings>().And(settings =>
{
settings.Detailed.ShouldBe(true);
settings.Extra.ShouldBe(new[] { "first-item" });
});
}
}
}

View File

@@ -0,0 +1,223 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Shouldly;
using Spectre.Console.Cli;
using Spectre.Console.Tests.Data;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public sealed class FlagValues
{
[SuppressMessage("Performance", "CA1812", Justification = "It's OK")]
private sealed class FlagSettings : CommandSettings
{
[CommandOption("--serve [PORT]")]
public FlagValue<int> Serve { get; set; }
}
[SuppressMessage("Performance", "CA1812", Justification = "It's OK")]
private sealed class FlagSettingsWithNullableValueType : CommandSettings
{
[CommandOption("--serve [PORT]")]
public FlagValue<int?> Serve { get; set; }
}
[SuppressMessage("Performance", "CA1812", Justification = "It's OK")]
private sealed class FlagSettingsWithOptionalOptionButNoFlagValue : CommandSettings
{
[CommandOption("--serve [PORT]")]
public int Serve { get; set; }
}
[SuppressMessage("Performance", "CA1812", Justification = "It's OK")]
private sealed class FlagSettingsWithDefaultValue : CommandSettings
{
[CommandOption("--serve [PORT]")]
[DefaultValue(987)]
public FlagValue<int> Serve { get; set; }
}
[Fact]
public void Should_Throw_If_Command_Option_Value_Is_Optional_But_Type_Is_Not_A_Flag_Value()
{
// Given
var app = new CommandApp();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<FlagSettingsWithOptionalOptionButNoFlagValue>>("foo");
});
// When
var result = Record.Exception(() => app.Run(new[] { "foo", "--serve", "123" }));
// Then
result.ShouldBeOfType<CommandConfigurationException>().And(ex =>
{
ex.Message.ShouldBe("The option 'serve' has an optional value but does not implement IFlagValue.");
});
}
[Fact]
public void Should_Set_Flag_And_Value_If_Both_Were_Provided()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<FlagSettings>>("foo");
});
// When
var result = app.Run(new[] { "foo", "--serve", "123", });
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<FlagSettings>().And(flag =>
{
flag.Serve.IsSet.ShouldBeTrue();
flag.Serve.Value.ShouldBe(123);
});
}
[Fact]
public void Should_Only_Set_Flag_If_No_Value_Was_Provided()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<FlagSettings>>("foo");
});
// When
var result = app.Run(new[] { "foo", "--serve" });
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<FlagSettings>().And(flag =>
{
flag.Serve.IsSet.ShouldBeTrue();
flag.Serve.Value.ShouldBe(0);
});
}
[Fact]
public void Should_Set_Value_To_Default_Value_If_None_Was_Explicitly_Set()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<FlagSettingsWithDefaultValue>>("foo");
});
// When
var result = app.Run(new[] { "foo", "--serve" });
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<FlagSettingsWithDefaultValue>().And(flag =>
{
flag.Serve.IsSet.ShouldBeTrue();
flag.Serve.Value.ShouldBe(987);
});
}
[Fact]
public void Should_Create_Unset_Instance_If_Flag_Was_Not_Set()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<FlagSettings>>("foo");
});
// When
var result = app.Run(new[] { "foo" });
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<FlagSettings>().And(flag =>
{
flag.Serve.IsSet.ShouldBeFalse();
flag.Serve.Value.ShouldBe(0);
});
}
[Fact]
public void Should_Create_Unset_Instance_With_Null_For_Nullable_Value_Type_If_Flag_Was_Not_Set()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<FlagSettingsWithNullableValueType>>("foo");
});
// When
var result = app.Run(new[] { "foo" });
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<FlagSettingsWithNullableValueType>().And(flag =>
{
flag.Serve.IsSet.ShouldBeFalse();
flag.Serve.Value.ShouldBeNull();
});
}
[Theory]
[InlineData("Foo", true, "Set=True, Value=Foo")]
[InlineData("Bar", false, "Set=False, Value=Bar")]
public void Should_Return_Correct_String_Representation_From_ToString(
string value,
bool isSet,
string expected)
{
// Given
var flag = new FlagValue<string>
{
Value = value,
IsSet = isSet,
};
// When
var result = flag.ToString();
// Then
result.ShouldBe(expected);
}
[Theory]
[InlineData(true, "Set=True")]
[InlineData(false, "Set=False")]
public void Should_Return_Correct_String_Representation_From_ToString_If_Value_Is_Not_Set(
bool isSet,
string expected)
{
// Given
var flag = new FlagValue<string>
{
IsSet = isSet,
};
// When
var result = flag.ToString();
// Then
result.ShouldBe(expected);
}
}
}
}

View File

@@ -0,0 +1,283 @@
using System.Threading.Tasks;
using Spectre.Console.Cli;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
[UsesVerify]
[ExpectationPath("Cli/Help")]
public class Help
{
[Fact]
[Expectation("Root")]
public Task Should_Output_Root_Correctly()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
configurator.AddCommand<HorseCommand>("horse");
configurator.AddCommand<GiraffeCommand>("giraffe");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Hidden_Commands")]
public Task Should_Skip_Hidden_Commands()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
configurator.AddCommand<HorseCommand>("horse");
configurator.AddCommand<GiraffeCommand>("giraffe")
.WithExample(new[] { "giraffe", "123" })
.IsHidden();
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Command")]
public Task Should_Output_Command_Correctly()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddBranch<CatSettings>("cat", animal =>
{
animal.SetDescription("Contains settings for a cat.");
animal.AddCommand<LionCommand>("lion");
});
});
// When
var result = fixture.Run("cat", "--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Leaf")]
public Task Should_Output_Leaf_Correctly()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddBranch<CatSettings>("cat", animal =>
{
animal.SetDescription("Contains settings for a cat.");
animal.AddCommand<LionCommand>("lion");
});
});
// When
var result = fixture.Run("cat", "lion", "--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Default")]
public Task Should_Output_Default_Command_Correctly()
{
// Given
var fixture = new CommandAppTester();
fixture.SetDefaultCommand<LionCommand>();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("RootExamples")]
public Task Should_Output_Root_Examples_Defined_On_Root()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
configurator.AddExample(new[] { "horse", "--name", "Brutus" });
configurator.AddCommand<DogCommand>("dog");
configurator.AddCommand<HorseCommand>("horse");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("RootExamples_Children")]
public Task Should_Output_Root_Examples_Defined_On_Direct_Children_If_Root_Have_No_Examples()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog")
.WithExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
configurator.AddCommand<HorseCommand>("horse")
.WithExample(new[] { "horse", "--name", "Brutus" });
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("RootExamples_Leafs")]
public Task Should_Output_Root_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
animal.AddCommand<DogCommand>("dog")
.WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
animal.AddCommand<HorseCommand>("horse")
.WithExample(new[] { "animal", "horse", "--name", "Brutus" });
});
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("CommandExamples")]
public Task Should_Only_Output_Command_Examples_Defined_On_Command()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
animal.AddExample(new[] { "animal", "--help" });
animal.AddCommand<DogCommand>("dog")
.WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
animal.AddCommand<HorseCommand>("horse")
.WithExample(new[] { "animal", "horse", "--name", "Brutus" });
});
});
// When
var result = fixture.Run("animal", "--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("DefaultExamples")]
public Task Should_Output_Root_Examples_If_Default_Command_Is_Specified()
{
// Given
var fixture = new CommandAppTester();
fixture.SetDefaultCommand<LionCommand>();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddExample(new[] { "12", "-c", "3" });
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("NoDescription")]
public Task Should_Not_Show_Truncated_Command_Table_If_Commands_Are_Missing_Description()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddCommand<NoDescriptionCommand>("bar");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("ArgumentOrder")]
public Task Should_List_Arguments_In_Correct_Order()
{
// Given
var fixture = new CommandAppTester();
fixture.SetDefaultCommand<GenericCommand<ArgumentOrderSettings>>();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
}
}
}

View File

@@ -0,0 +1,67 @@
using Shouldly;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Xunit;
using Spectre.Console.Cli;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public sealed class Injection
{
public sealed class FakeDependency
{
}
public sealed class InjectSettings : CommandSettings
{
public FakeDependency Fake { get; set; }
[CommandOption("--name <NAME>")]
public string Name { get; }
[CommandOption("--age <AGE>")]
public int Age { get; set; }
public InjectSettings(FakeDependency fake, string name)
{
Fake = fake;
Name = "Hello " + name;
}
}
[Fact]
public void Should_Inject_Parameters()
{
// Given
var app = new CommandAppTester();
var dependency = new FakeDependency();
app.SetDefaultCommand<GenericCommand<InjectSettings>>();
app.Configure(config =>
{
config.Settings.Registrar.RegisterInstance(dependency);
config.PropagateExceptions();
});
// When
var result = app.Run(new[]
{
"--name", "foo",
"--age", "35",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<InjectSettings>().And(injected =>
{
injected.ShouldNotBeNull();
injected.Fake.ShouldBeSameAs(dependency);
injected.Name.ShouldBe("Hello foo");
injected.Age.ShouldBe(35);
});
}
}
}
}

View File

@@ -0,0 +1,292 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Shouldly;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Xunit;
using Spectre.Console.Cli;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public sealed class Pairs
{
public sealed class AmbiguousSettings : CommandSettings
{
[CommandOption("--var <VALUE>")]
[PairDeconstructor(typeof(StringIntDeconstructor))]
[TypeConverter(typeof(CatAgilityConverter))]
public ILookup<string, string> Values { get; set; }
}
public sealed class NotDeconstructableSettings : CommandSettings
{
[CommandOption("--var <VALUE>")]
[PairDeconstructor(typeof(StringIntDeconstructor))]
public string Values { get; set; }
}
public sealed class DefaultPairDeconstructorSettings : CommandSettings
{
[CommandOption("--var <VALUE>")]
public IDictionary<string, int> Values { get; set; }
}
public sealed class LookupSettings : CommandSettings
{
[CommandOption("--var <VALUE>")]
[PairDeconstructor(typeof(StringIntDeconstructor))]
public ILookup<string, string> Values { get; set; }
}
public sealed class DictionarySettings : CommandSettings
{
[CommandOption("--var <VALUE>")]
[PairDeconstructor(typeof(StringIntDeconstructor))]
public IDictionary<string, string> Values { get; set; }
}
public sealed class ReadOnlyDictionarySettings : CommandSettings
{
[CommandOption("--var <VALUE>")]
[PairDeconstructor(typeof(StringIntDeconstructor))]
public IReadOnlyDictionary<string, string> Values { get; set; }
}
public sealed class StringIntDeconstructor : PairDeconstuctor<string, string>
{
protected override (string Key, string Value) Deconstruct(string value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
var parts = value.Split(new[] { '=' });
if (parts.Length != 2)
{
throw new InvalidOperationException("Could not parse pair");
}
return (parts[0], parts[1]);
}
}
[Fact]
public void Should_Throw_If_Option_Has_Pair_Deconstructor_And_Type_Converter()
{
// Given
var app = new CommandApp<GenericCommand<AmbiguousSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = Record.Exception(() => app.Run(new[]
{
"--var", "foo=bar",
"--var", "foo=qux",
}));
// Then
result.ShouldBeOfType<CommandConfigurationException>().And(ex =>
{
ex.Message.ShouldBe("The option 'var' is both marked as pair deconstructable and convertable.");
});
}
[Fact]
public void Should_Throw_If_Option_Has_Pair_Deconstructor_But_Type_Is_Not_Deconstructable()
{
// Given
var app = new CommandApp<GenericCommand<NotDeconstructableSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = Record.Exception(() => app.Run(new[]
{
"--var", "foo=bar",
"--var", "foo=qux",
}));
// Then
result.ShouldBeOfType<CommandConfigurationException>().And(ex =>
{
ex.Message.ShouldBe("The option 'var' is marked as pair deconstructable, but the underlying type does not support that.");
});
}
[Fact]
public void Should_Map_Pairs_To_Pair_Deconstructable_Collection_Using_Default_Deconstructort()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<DefaultPairDeconstructorSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run(new[]
{
"--var", "foo=1",
"--var", "foo=3",
"--var", "bar=4",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DefaultPairDeconstructorSettings>().And(pair =>
{
pair.Values.ShouldNotBeNull();
pair.Values.Count.ShouldBe(2);
pair.Values["foo"].ShouldBe(3);
pair.Values["bar"].ShouldBe(4);
});
}
[Theory]
[InlineData("foo=1=2", "Error: The value 'foo=1=2' is not in a correct format")]
[InlineData("foo=1=2=3", "Error: The value 'foo=1=2=3' is not in a correct format")]
public void Should_Throw_If_Value_Is_Not_In_A_Valid_Format_Using_Default_Deconstructor(
string input, string expected)
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<DefaultPairDeconstructorSettings>>();
// When
var result = app.Run(new[]
{
"--var", input,
});
// Then
result.ExitCode.ShouldBe(-1);
result.Output.ShouldBe(expected);
}
[Fact]
public void Should_Map_Lookup_Values()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<LookupSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run(new[]
{
"--var", "foo=bar",
"--var", "foo=qux",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<LookupSettings>().And(pair =>
{
pair.Values.ShouldNotBeNull();
pair.Values.Count.ShouldBe(1);
pair.Values["foo"].ToList().Count.ShouldBe(2);
});
}
[Fact]
public void Should_Map_Dictionary_Values()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<DictionarySettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run(new[]
{
"--var", "foo=bar",
"--var", "baz=qux",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DictionarySettings>().And(pair =>
{
pair.Values.ShouldNotBeNull();
pair.Values.Count.ShouldBe(2);
pair.Values["foo"].ShouldBe("bar");
pair.Values["baz"].ShouldBe("qux");
});
}
[Fact]
public void Should_Map_Latest_Value_Of_Same_Key_When_Mapping_To_Dictionary()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<DictionarySettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run(new[]
{
"--var", "foo=bar",
"--var", "foo=qux",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DictionarySettings>().And(pair =>
{
pair.Values.ShouldNotBeNull();
pair.Values.Count.ShouldBe(1);
pair.Values["foo"].ShouldBe("qux");
});
}
[Fact]
public void Should_Map_ReadOnly_Dictionary_Values()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<ReadOnlyDictionarySettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run(new[]
{
"--var", "foo=bar",
"--var", "baz=qux",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<ReadOnlyDictionarySettings>().And(pair =>
{
pair.Values.ShouldNotBeNull();
pair.Values.Count.ShouldBe(2);
pair.Values["foo"].ShouldBe("bar");
pair.Values["baz"].ShouldBe("qux");
});
}
}
}
}

View File

@@ -0,0 +1,677 @@
using System;
using System.Threading.Tasks;
using Shouldly;
using Spectre.Console.Cli;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
[UsesVerify]
[ExpectationPath("Cli/Parsing")]
public sealed class Parsing
{
[UsesVerify]
[ExpectationPath("UnknownCommand")]
public sealed class UnknownCommand
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text_When_Command_Is_Unknown()
{
// Given
var fixture = new Fixture();
fixture.Configure(config =>
{
config.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("cat", "14");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_2")]
public Task Should_Return_Correct_Text_For_Unknown_Command_When_Current_Command_Has_No_Arguments()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<EmptyCommand>("empty");
});
// When
var result = fixture.Run("empty", "other");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_3")]
public Task Should_Return_Correct_Text_With_Suggestion_When_Command_Followed_By_Argument_Is_Unknown_And_Distance_Is_Small()
{
// Given
var fixture = new Fixture();
fixture.Configure(config =>
{
config.AddBranch<CommandSettings>("dog", a =>
{
a.AddCommand<CatCommand>("cat");
});
});
// When
var result = fixture.Run("dog", "bat", "14");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_4")]
public Task Should_Return_Correct_Text_With_Suggestion_When_Root_Command_Followed_By_Argument_Is_Unknown_And_Distance_Is_Small()
{
// Given
var fixture = new Fixture();
fixture.Configure(config =>
{
config.AddCommand<CatCommand>("cat");
});
// When
var result = fixture.Run("bat", "14");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_5")]
public Task Should_Return_Correct_Text_With_Suggestion_And_No_Arguments_When_Root_Command_Is_Unknown_And_Distance_Is_Small()
{
// Given
var fixture = new Fixture();
fixture.WithDefaultCommand<GenericCommand<EmptyCommandSettings>>();
fixture.Configure(config =>
{
config.AddCommand<GenericCommand<EmptyCommandSettings>>("cat");
});
// When
var result = fixture.Run("bat");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_6")]
public Task Should_Return_Correct_Text_With_Suggestion_And_No_Arguments_When_Command_Is_Unknown_And_Distance_Is_Small()
{
// Given
var fixture = new Fixture();
fixture.WithDefaultCommand<GenericCommand<EmptyCommandSettings>>();
fixture.Configure(configurator =>
{
configurator.AddBranch<CommandSettings>("dog", a =>
{
a.AddCommand<CatCommand>("cat");
});
});
// When
var result = fixture.Run("dog", "bat");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_7")]
public Task Should_Return_Correct_Text_With_Suggestion_When_Root_Command_After_Argument_Is_Unknown_And_Distance_Is_Small()
{
// Given
var fixture = new Fixture();
fixture.WithDefaultCommand<GenericCommand<FooCommandSettings>>();
fixture.Configure(configurator =>
{
configurator.AddCommand<GenericCommand<BarCommandSettings>>("bar");
});
// When
var result = fixture.Run("qux", "bat");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_8")]
public Task Should_Return_Correct_Text_With_Suggestion_When_Command_After_Argument_Is_Unknown_And_Distance_Is_Small()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddBranch<FooCommandSettings>("foo", a =>
{
a.AddCommand<GenericCommand<BarCommandSettings>>("bar");
});
});
// When
var result = fixture.Run("foo", "qux", "bat");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("CannotAssignValueToFlag")]
public sealed class CannotAssignValueToFlag
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text_For_Long_Option()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", "--alive", "foo");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_2")]
public Task Should_Return_Correct_Text_For_Short_Option()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", "-a", "foo");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("NoValueForOption")]
public sealed class NoValueForOption
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text_For_Long_Option()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", "--name");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_2")]
public Task Should_Return_Correct_Text_For_Short_Option()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", "-n");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("NoMatchingArgument")]
public sealed class NoMatchingArgument
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<GiraffeCommand>("giraffe");
});
// When
var result = fixture.Run("giraffe", "foo", "bar", "baz");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("UnexpectedOption")]
public sealed class UnexpectedOption
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text_For_Long_Option()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("--foo");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_2")]
public Task Should_Return_Correct_Text_For_Short_Option()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("-f");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("UnknownOption")]
public sealed class UnknownOption
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text_For_Long_Option_If_Strict_Mode_Is_Enabled()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.UseStrictParsing();
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", "--unknown");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_2")]
public Task Should_Return_Correct_Text_For_Short_Option_If_Strict_Mode_Is_Enabled()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.UseStrictParsing();
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", "-u");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("UnterminatedQuote")]
public sealed class UnterminatedQuote
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("--name", "\"Rufus");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("OptionWithoutName")]
public sealed class OptionWithoutName
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text_For_Short_Option()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", "-", " ");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_2")]
public Task Should_Return_Correct_Text_For_Missing_Long_Option_Value_With_Equality_Separator()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", $"--foo=");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_3")]
public Task Should_Return_Correct_Text_For_Missing_Long_Option_Value_With_Colon_Separator()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", $"--foo:");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_4")]
public Task Should_Return_Correct_Text_For_Missing_Short_Option_Value_With_Equality_Separator()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", $"-f=");
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Test_5")]
public Task Should_Return_Correct_Text_For_Missing_Short_Option_Value_With_Colon_Separator()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", $"-f:");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("InvalidShortOptionName")]
public sealed class InvalidShortOptionName
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", $"-f0o");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("LongOptionNameIsOneCharacter")]
public sealed class LongOptionNameIsOneCharacter
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", $"--f");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("LongOptionNameIsMissing")]
public sealed class LongOptionNameIsMissing
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", $"-- ");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("LongOptionNameStartWithDigit")]
public sealed class LongOptionNameStartWithDigit
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", $"--1foo");
// Then
return Verifier.Verify(result);
}
}
[UsesVerify]
[ExpectationPath("LongOptionNameContainSymbol")]
public sealed class LongOptionNameContainSymbol
{
[Fact]
[Expectation("Test_1")]
public Task Should_Return_Correct_Text()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", $"--f€oo");
// Then
return Verifier.Verify(result);
}
[Theory]
[InlineData("--f-oo")]
[InlineData("--f-o-o")]
[InlineData("--f_oo")]
[InlineData("--f_o_o")]
public void Should_Allow_Special_Symbols_In_Name(string option)
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run("dog", option);
// Then
result.ShouldBe("Error: Command 'dog' is missing required argument 'AGE'.");
}
}
[Fact]
[Expectation("Quoted_Strings")]
public Task Should_Parse_Quoted_Strings_Correctly()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DumpRemainingCommand>("foo");
});
// When
var result = fixture.Run("foo", "--", "/c", "\"set && pause\"");
// Then
return Verifier.Verify(result);
}
}
internal sealed class Fixture
{
private Action<CommandApp> _appConfiguration = _ => { };
private Action<IConfigurator> _configuration;
public void WithDefaultCommand<T>()
where T : class, ICommand
{
_appConfiguration = (app) => app.SetDefaultCommand<T>();
}
public void Configure(Action<IConfigurator> action)
{
_configuration = action;
}
public string Run(params string[] args)
{
using (var console = new TestConsole())
{
var app = new CommandApp();
_appConfiguration?.Invoke(app);
app.Configure(_configuration);
app.Configure(c => c.ConfigureConsole(console));
app.Run(args);
return console.Output
.NormalizeLineEndings()
.TrimLines()
.Trim();
}
}
}
}
}

View File

@@ -0,0 +1,77 @@
using Shouldly;
using Spectre.Console.Cli;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public sealed class Remaining
{
[Fact]
public void Should_Register_Remaining_Parsed_Arguments_With_Context()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
});
});
// When
var result = app.Run(new[]
{
"animal", "4", "dog", "12", "--",
"--foo", "bar", "--foo", "baz",
"-bar", "\"baz\"", "qux",
"foo bar baz qux",
});
// Then
result.Context.Remaining.Parsed.Count.ShouldBe(4);
result.Context.ShouldHaveRemainingArgument("foo", values: new[] { "bar", "baz" });
result.Context.ShouldHaveRemainingArgument("b", values: new[] { (string)null });
result.Context.ShouldHaveRemainingArgument("a", values: new[] { (string)null });
result.Context.ShouldHaveRemainingArgument("r", values: new[] { (string)null });
}
[Fact]
public void Should_Register_Remaining_Raw_Arguments_With_Context()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
});
});
// When
var result = app.Run(new[]
{
"animal", "4", "dog", "12", "--",
"--foo", "bar", "-bar", "\"baz\"", "qux",
"foo bar baz qux",
});
// Then
result.Context.Remaining.Raw.Count.ShouldBe(6);
result.Context.Remaining.Raw[0].ShouldBe("--foo");
result.Context.Remaining.Raw[1].ShouldBe("bar");
result.Context.Remaining.Raw[2].ShouldBe("-bar");
result.Context.Remaining.Raw[3].ShouldBe("baz");
result.Context.Remaining.Raw[4].ShouldBe("qux");
result.Context.Remaining.Raw[5].ShouldBe("foo bar baz qux");
}
}
}
}

View File

@@ -0,0 +1,118 @@
using Shouldly;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Xunit;
using Spectre.Console.Cli;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandApptests
{
[Fact]
public void Should_Treat_Commands_As_Case_Sensitive_If_Specified()
{
// Given
var app = new CommandApp();
app.Configure(config =>
{
config.UseStrictParsing();
config.PropagateExceptions();
config.CaseSensitivity(CaseSensitivity.Commands);
config.AddCommand<GenericCommand<StringOptionSettings>>("command");
});
// When
var result = Record.Exception(() => app.Run(new[]
{
"Command", "--foo", "bar",
}));
// Then
result.ShouldNotBeNull();
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe("Unknown command 'Command'.");
});
}
[Fact]
public void Should_Treat_Long_Options_As_Case_Sensitive_If_Specified()
{
// Given
var app = new CommandApp();
app.Configure(config =>
{
config.UseStrictParsing();
config.PropagateExceptions();
config.CaseSensitivity(CaseSensitivity.LongOptions);
config.AddCommand<GenericCommand<StringOptionSettings>>("command");
});
// When
var result = Record.Exception(() => app.Run(new[]
{
"command", "--Foo", "bar",
}));
// Then
result.ShouldNotBeNull();
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe("Unknown option 'Foo'.");
});
}
[Fact]
public void Should_Treat_Short_Options_As_Case_Sensitive()
{
// Given
var app = new CommandApp();
app.Configure(config =>
{
config.UseStrictParsing();
config.PropagateExceptions();
config.AddCommand<GenericCommand<StringOptionSettings>>("command");
});
// When
var result = Record.Exception(() => app.Run(new[]
{
"command", "-F", "bar",
}));
// Then
result.ShouldNotBeNull();
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe("Unknown option 'F'.");
});
}
[Fact]
public void Should_Suppress_Case_Sensitivity_If_Specified()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.UseStrictParsing();
config.PropagateExceptions();
config.CaseSensitivity(CaseSensitivity.None);
config.AddCommand<GenericCommand<StringOptionSettings>>("command");
});
// When
var result = app.Run(new[]
{
"Command", "--Foo", "bar",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<StringOptionSettings>().And(vec =>
{
vec.Foo.ShouldBe("bar");
});
}
}
}

View File

@@ -0,0 +1,26 @@
using Shouldly;
using Spectre.Console.Cli;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
[Fact]
public void Should_Apply_Case_Sensitivity_For_Everything_By_Default()
{
// Given
var app = new CommandApp();
// When
var defaultSensitivity = CaseSensitivity.None;
app.Configure(config =>
{
defaultSensitivity = config.Settings.CaseSensitivity;
});
// Then
defaultSensitivity.ShouldBe(CaseSensitivity.All);
}
}
}

View File

@@ -0,0 +1,40 @@
using Shouldly;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Xunit;
using Spectre.Console.Cli;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public sealed class TypeConverters
{
[Fact]
public void Should_Bind_Using_Custom_Type_Converter_If_Specified()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<CatCommand>("cat");
});
// When
var result = app.Run(new[]
{
"cat", "--name", "Tiger",
"--agility", "FOOBAR",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<CatSettings>().And(cat =>
{
cat.Agility.ShouldBe(6);
});
}
}
}
}

View File

@@ -0,0 +1,299 @@
using Shouldly;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Spectre.Console.Cli.Unsafe;
using Xunit;
using Spectre.Console.Cli;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public sealed class SafetyOff
{
[Fact]
public void Can_Mix_Safe_And_Unsafe_Configurators()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SafetyOff().AddBranch("mammal", typeof(MammalSettings), mammal =>
{
mammal.AddCommand("dog", typeof(DogCommand));
mammal.AddCommand("horse", typeof(HorseCommand));
});
});
});
// When
var result = app.Run(new[]
{
"animal", "--alive", "mammal", "--name",
"Rufus", "dog", "12", "--good-boy",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.Age.ShouldBe(12);
dog.GoodBoy.ShouldBe(true);
dog.Name.ShouldBe("Rufus");
dog.IsAlive.ShouldBe(true);
});
}
[Fact]
public void Can_Turn_Safety_On_After_Turning_It_Off_For_Branch()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.SafetyOff().AddBranch("animal", typeof(AnimalSettings), animal =>
{
animal.SafetyOn<AnimalSettings>()
.AddBranch<MammalSettings>("mammal", mammal =>
{
mammal.SafetyOff().AddCommand("dog", typeof(DogCommand));
mammal.AddCommand<HorseCommand>("horse");
});
});
});
// When
var result = app.Run(new[]
{
"animal", "--alive", "mammal", "--name",
"Rufus", "dog", "12", "--good-boy",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.Age.ShouldBe(12);
dog.GoodBoy.ShouldBe(true);
dog.Name.ShouldBe("Rufus");
dog.IsAlive.ShouldBe(true);
});
}
[Fact]
public void Should_Throw_If_Trying_To_Convert_Unsafe_Branch_Configurator_To_Safe_Version_With_Wrong_Type()
{
// Given
var app = new CommandApp();
// When
var result = Record.Exception(() => app.Configure(config =>
{
config.PropagateExceptions();
config.SafetyOff().AddBranch("animal", typeof(AnimalSettings), animal =>
{
animal.SafetyOn<MammalSettings>().AddCommand<DogCommand>("dog");
});
}));
// Then
result.ShouldBeOfType<CommandConfigurationException>();
result.Message.ShouldBe("Configurator cannot be converted to a safe configurator of type 'MammalSettings'.");
}
[Fact]
public void Should_Pass_Case_1()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.SafetyOff().AddBranch("animal", typeof(AnimalSettings), animal =>
{
animal.AddBranch("mammal", typeof(MammalSettings), mammal =>
{
mammal.AddCommand("dog", typeof(DogCommand));
mammal.AddCommand("horse", typeof(HorseCommand));
});
});
});
// When
var result = app.Run(new[]
{
"animal", "--alive", "mammal", "--name",
"Rufus", "dog", "12", "--good-boy",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.Age.ShouldBe(12);
dog.GoodBoy.ShouldBe(true);
dog.Name.ShouldBe("Rufus");
dog.IsAlive.ShouldBe(true);
});
}
[Fact]
public void Should_Pass_Case_2()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.SafetyOff().AddCommand("dog", typeof(DogCommand));
});
// When
var result = app.Run(new[]
{
"dog", "12", "4", "--good-boy",
"--name", "Rufus", "--alive",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.Legs.ShouldBe(12);
dog.Age.ShouldBe(4);
dog.GoodBoy.ShouldBe(true);
dog.Name.ShouldBe("Rufus");
dog.IsAlive.ShouldBe(true);
});
}
[Fact]
public void Should_Pass_Case_3()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.SafetyOff().AddBranch("animal", typeof(AnimalSettings), animal =>
{
animal.AddCommand("dog", typeof(DogCommand));
animal.AddCommand("horse", typeof(HorseCommand));
});
});
// When
var result = app.Run(new[]
{
"animal", "dog", "12", "--good-boy",
"--name", "Rufus",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.Age.ShouldBe(12);
dog.GoodBoy.ShouldBe(true);
dog.Name.ShouldBe("Rufus");
dog.IsAlive.ShouldBe(false);
});
}
[Fact]
public void Should_Pass_Case_4()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.SafetyOff().AddBranch("animal", typeof(AnimalSettings), animal =>
{
animal.AddCommand("dog", typeof(DogCommand));
});
});
// When
var result = app.Run(new[]
{
"animal", "4", "dog", "12",
"--good-boy", "--name", "Rufus",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.Legs.ShouldBe(4);
dog.Age.ShouldBe(12);
dog.GoodBoy.ShouldBe(true);
dog.IsAlive.ShouldBe(false);
dog.Name.ShouldBe("Rufus");
});
}
[Fact]
public void Should_Pass_Case_5()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.SafetyOff().AddCommand("multi", typeof(OptionVectorCommand));
});
// When
var result = app.Run(new[]
{
"multi", "--foo", "a", "--foo", "b", "--bar", "1", "--foo", "c", "--bar", "2",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<OptionVectorSettings>().And(vec =>
{
vec.Foo.Length.ShouldBe(3);
vec.Foo.ShouldBe(new[] { "a", "b", "c" });
vec.Bar.Length.ShouldBe(2);
vec.Bar.ShouldBe(new[] { 1, 2 });
});
}
[Fact]
public void Should_Pass_Case_6()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<ArgumentVectorSettings>>("multi");
});
// When
var result = app.Run(new[]
{
"multi", "a", "b", "c",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<ArgumentVectorSettings>().And(vec =>
{
vec.Foo.Length.ShouldBe(3);
vec.Foo.ShouldBe(new[] { "a", "b", "c" });
});
}
}
}
}

View File

@@ -0,0 +1,88 @@
using Shouldly;
using Spectre.Console.Cli;
using Spectre.Console.Tests.Data;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public sealed class Validation
{
[Fact]
public void Should_Throw_If_Attribute_Validation_Fails()
{
// Given
var app = new CommandApp();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<HorseCommand>("horse");
});
});
// When
var result = Record.Exception(() => app.Run(new[] { "animal", "3", "dog", "7", "--name", "Rufus" }));
// Then
result.ShouldBeOfType<CommandRuntimeException>().And(e =>
{
e.Message.ShouldBe("Animals must have an even number of legs.");
});
}
[Fact]
public void Should_Throw_If_Settings_Validation_Fails()
{
// Given
var app = new CommandApp();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<HorseCommand>("horse");
});
});
// When
var result = Record.Exception(() => app.Run(new[] { "animal", "4", "dog", "7", "--name", "Tiger" }));
// Then
result.ShouldBeOfType<CommandRuntimeException>().And(e =>
{
e.Message.ShouldBe("Tiger is not a dog name!");
});
}
[Fact]
public void Should_Throw_If_Command_Validation_Fails()
{
// Given
var app = new CommandApp();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<HorseCommand>("horse");
});
});
// When
var result = Record.Exception(() => app.Run(new[] { "animal", "4", "dog", "101", "--name", "Rufus" }));
// Then
result.ShouldBeOfType<CommandRuntimeException>().And(e =>
{
e.Message.ShouldBe("Dog is too old...");
});
}
}
}
}

View File

@@ -0,0 +1,97 @@
using System.ComponentModel;
using System.Globalization;
using Shouldly;
using Spectre.Console.Cli;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public sealed class ValueProviders
{
public sealed class ValueProviderSettings : CommandSettings
{
[CommandOption("-f|--foo <VALUE>")]
[IntegerValueProvider(32)]
[TypeConverter(typeof(HexConverter))]
public string Foo { get; set; }
}
public sealed class IntegerValueProvider : ParameterValueProviderAttribute
{
private readonly int _value;
public IntegerValueProvider(int value)
{
_value = value;
}
public override bool TryGetValue(CommandParameterContext context, out object result)
{
if (context.Value == null)
{
result = _value;
return true;
}
result = null;
return false;
}
}
public sealed class HexConverter : TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is int integer)
{
return integer.ToString("X");
}
return value is string stringValue && int.TryParse(stringValue, out var intValue)
? intValue.ToString("X")
: base.ConvertFrom(context, culture, value);
}
}
[Fact]
public void Should_Use_Provided_Value_If_No_Value_Was_Specified()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<ValueProviderSettings>>();
app.Configure(config => config.PropagateExceptions());
// When
var result = app.Run();
// Then
result.Settings.ShouldBeOfType<ValueProviderSettings>().And(settings =>
{
settings.Foo.ShouldBe("20"); // 32 is 0x20
});
}
[Fact]
public void Should_Not_Override_Value_If_Value_Was_Specified()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<ValueProviderSettings>>();
app.Configure(config => config.PropagateExceptions());
// When
var result = app.Run("--foo", "12");
// Then
result.Settings.ShouldBeOfType<ValueProviderSettings>().And(settings =>
{
settings.Foo.ShouldBe("C"); // 12 is 0xC
});
}
}
}
}

View File

@@ -0,0 +1,111 @@
using Shouldly;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Xunit;
using Spectre.Console.Cli;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public sealed class Vectors
{
[Fact]
public void Should_Throw_If_A_Single_Command_Has_Multiple_Argument_Vectors()
{
// Given
var app = new CommandApp();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<MultipleArgumentVectorSettings>>("multi");
});
// When
var result = Record.Exception(() => app.Run(new[] { "multi", "a", "b", "c" }));
// Then
result.ShouldBeOfType<CommandConfigurationException>().And(ex =>
{
ex.Message.ShouldBe("The command 'multi' specifies more than one vector argument.");
});
}
[Fact]
public void Should_Throw_If_An_Argument_Vector_Is_Not_Specified_Last()
{
// Given
var app = new CommandApp();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<MultipleArgumentVectorSpecifiedFirstSettings>>("multi");
});
// When
var result = Record.Exception(() => app.Run(new[] { "multi", "a", "b", "c" }));
// Then
result.ShouldBeOfType<CommandConfigurationException>().And(ex =>
{
ex.Message.ShouldBe("The command 'multi' specifies an argument vector that is not the last argument.");
});
}
[Fact]
public void Should_Assign_Values_To_Argument_Vector()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<ArgumentVectorSettings>>("multi");
});
// When
var result = app.Run(new[]
{
"multi", "a", "b", "c",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<ArgumentVectorSettings>().And(vec =>
{
vec.Foo.Length.ShouldBe(3);
vec.Foo[0].ShouldBe("a");
vec.Foo[1].ShouldBe("b");
vec.Foo[2].ShouldBe("c");
});
}
[Fact]
public void Should_Assign_Values_To_Option_Vector()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<OptionVectorCommand>("cmd");
});
// When
var result = app.Run(new[]
{
"cmd", "--foo", "red",
"--bar", "4", "--foo", "blue",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<OptionVectorSettings>().And(vec =>
{
vec.Foo.ShouldBe(new string[] { "red", "blue" });
vec.Bar.ShouldBe(new int[] { 4 });
});
}
}
}
}

View File

@@ -0,0 +1,37 @@
using Shouldly;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public sealed class Version
{
[Fact]
public void Should_Output_The_Version_To_The_Console()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(config =>
{
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddBranch<MammalSettings>("mammal", mammal =>
{
mammal.AddCommand<DogCommand>("dog");
mammal.AddCommand<HorseCommand>("horse");
});
});
});
// When
var result = fixture.Run(Constants.VersionCommand);
// Then
result.Output.ShouldStartWith("Spectre.Cli version ");
}
}
}
}

View File

@@ -0,0 +1,142 @@
using System.Threading.Tasks;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using VerifyXunit;
using Xunit;
using Spectre.Console.Cli;
using Spectre.Verify.Extensions;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
[UsesVerify]
[ExpectationPath("Cli/Xml")]
public sealed class Xml
{
[Fact]
[Expectation("Test_1")]
public Task Should_Dump_Correct_Model_For_Case_1()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddBranch<MammalSettings>("mammal", mammal =>
{
mammal.AddCommand<DogCommand>("dog");
mammal.AddCommand<HorseCommand>("horse");
});
});
});
// When
var result = fixture.Run(Constants.XmlDocCommand);
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Test_2")]
public Task Should_Dump_Correct_Model_For_Case_2()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(config =>
{
config.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run(Constants.XmlDocCommand);
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Test_3")]
public Task Should_Dump_Correct_Model_For_Case_3()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(config =>
{
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<HorseCommand>("horse");
});
});
// When
var result = fixture.Run(Constants.XmlDocCommand);
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Test_4")]
public Task Should_Dump_Correct_Model_For_Case_4()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(config =>
{
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
});
});
// When
var result = fixture.Run(Constants.XmlDocCommand);
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Test_5")]
public Task Should_Dump_Correct_Model_For_Case_5()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(config =>
{
config.AddCommand<OptionVectorCommand>("cmd");
});
// When
var result = fixture.Run(Constants.XmlDocCommand);
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Test_6")]
public Task Should_Dump_Correct_Model_For_Model_With_Default_Command()
{
// Given
var fixture = new CommandAppTester();
fixture.SetDefaultCommand<DogCommand>();
fixture.Configure(config =>
{
config.AddCommand<HorseCommand>("horse");
});
// When
var result = fixture.Run(Constants.XmlDocCommand);
// Then
return Verifier.Verify(result.Output);
}
}
}
}

View File

@@ -0,0 +1,820 @@
using System;
using Shouldly;
using Spectre.Console.Cli;
using Spectre.Console.Tests.Data;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
[Fact]
public void Should_Pass_Case_1()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddBranch<MammalSettings>("mammal", mammal =>
{
mammal.AddCommand<DogCommand>("dog");
mammal.AddCommand<HorseCommand>("horse");
});
});
});
// When
var result = app.Run(new[]
{
"animal", "--alive", "mammal", "--name",
"Rufus", "dog", "12", "--good-boy",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.Age.ShouldBe(12);
dog.GoodBoy.ShouldBe(true);
dog.Name.ShouldBe("Rufus");
dog.IsAlive.ShouldBe(true);
});
}
[Fact]
public void Should_Pass_Case_2()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<DogCommand>("dog");
});
// When
var result = app.Run(new[]
{
"dog", "12", "4", "--good-boy",
"--name", "Rufus", "--alive",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.Legs.ShouldBe(12);
dog.Age.ShouldBe(4);
dog.GoodBoy.ShouldBe(true);
dog.Name.ShouldBe("Rufus");
dog.IsAlive.ShouldBe(true);
});
}
[Fact]
public void Should_Pass_Case_3()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<HorseCommand>("horse");
});
});
// When
var result = app.Run(new[]
{
"animal", "dog", "12", "--good-boy",
"--name", "Rufus",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.Age.ShouldBe(12);
dog.GoodBoy.ShouldBe(true);
dog.Name.ShouldBe("Rufus");
dog.IsAlive.ShouldBe(false);
});
}
[Fact]
public void Should_Pass_Case_4()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
});
});
// When
var result = app.Run(new[]
{
"animal", "4", "dog", "12", "--good-boy",
"--name", "Rufus",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.Legs.ShouldBe(4);
dog.Age.ShouldBe(12);
dog.GoodBoy.ShouldBe(true);
dog.IsAlive.ShouldBe(false);
dog.Name.ShouldBe("Rufus");
});
}
[Fact]
public void Should_Pass_Case_5()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<OptionVectorCommand>("multi");
});
// When
var result = app.Run(new[]
{
"multi", "--foo", "a", "--foo", "b",
"--bar", "1", "--foo", "c", "--bar", "2",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<OptionVectorSettings>().And(vec =>
{
vec.Foo.Length.ShouldBe(3);
vec.Foo.ShouldBe(new[] { "a", "b", "c" });
vec.Bar.Length.ShouldBe(2);
vec.Bar.ShouldBe(new[] { 1, 2 });
});
}
[Fact]
public void Should_Pass_Case_6()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<ArgumentVectorSettings>>("multi");
});
// When
var result = app.Run(new[]
{
"multi", "a", "b", "c",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<ArgumentVectorSettings>().And(vec =>
{
vec.Foo.Length.ShouldBe(3);
vec.Foo.ShouldBe(new[] { "a", "b", "c" });
});
}
[Fact]
public void Should_Be_Able_To_Use_Command_Alias()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<OptionVectorCommand>("multi").WithAlias("multiple");
});
// When
var result = app.Run(new[]
{
"multiple", "--foo", "a",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<OptionVectorSettings>().And(vec =>
{
vec.Foo.Length.ShouldBe(1);
vec.Foo.ShouldBe(new[] { "a" });
});
}
[Fact]
public void Should_Assign_Default_Value_To_Optional_Argument()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<OptionalArgumentWithDefaultValueSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run(Array.Empty<string>());
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<OptionalArgumentWithDefaultValueSettings>().And(settings =>
{
settings.Greeting.ShouldBe("Hello World");
});
}
[Fact]
public void Should_Assign_Property_Initializer_To_Optional_Argument()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<OptionalArgumentWithPropertyInitializerSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run(Array.Empty<string>());
// Then
result.ExitCode.ShouldBe(0);
result.Settings
.ShouldBeOfType<OptionalArgumentWithPropertyInitializerSettings>()
.And(settings => settings.Count.ShouldBe(1))
.And(settings => settings.Value.ShouldBe(0))
.And(settings => settings.Names.ShouldNotBeNull())
.And(settings => settings.Names.ShouldNotBeNull())
.And(settings => settings.Names.ShouldBeEmpty());
}
[Fact]
public void Should_Overwrite_Property_Initializer_With_Argument_Value()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<OptionalArgumentWithPropertyInitializerSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run("-c", "0", "-v", "50", "ABBA", "Herreys");
// Then
result.ExitCode.ShouldBe(0);
result.Settings
.ShouldBeOfType<OptionalArgumentWithPropertyInitializerSettings>()
.And(settings => settings.Count.ShouldBe(0))
.And(settings => settings.Value.ShouldBe(50))
.And(settings => settings.Names.ShouldContain("ABBA"))
.And(settings => settings.Names.ShouldContain("Herreys"));
}
[Fact]
public void Should_Assign_Default_Value_To_Optional_Argument_Using_Converter_If_Necessary()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<OptionalArgumentWithDefaultValueAndTypeConverterSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run(Array.Empty<string>());
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<OptionalArgumentWithDefaultValueAndTypeConverterSettings>().And(settings =>
{
settings.Greeting.ShouldBe(5);
});
}
[Fact]
public void Should_Throw_If_Required_Argument_Have_Default_Value()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<RequiredArgumentWithDefaultValueSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = Record.Exception(() => app.Run(Array.Empty<string>()));
// Then
result.ShouldBeOfType<CommandConfigurationException>().And(ex =>
{
ex.Message.ShouldBe("The required argument 'GREETING' cannot have a default value.");
});
}
[Fact]
public void Should_Throw_If_Alias_Conflicts_With_Another_Command()
{
// Given
var app = new CommandApp();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<DogCommand>("dog").WithAlias("cat");
config.AddCommand<CatCommand>("cat");
});
// When
var result = Record.Exception(() => app.Run(new[] { "dog", "4", "12" }));
// Then
result.ShouldBeOfType<CommandConfigurationException>().And(ex =>
{
ex.Message.ShouldBe("The alias 'cat' for 'dog' conflicts with another command.");
});
}
[Fact]
public void Should_Register_Commands_When_Configuring_Application()
{
// Given
var registrar = new FakeTypeRegistrar();
var app = new CommandApp(registrar);
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<FooCommandSettings>>("foo");
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<HorseCommand>("horse");
});
});
// When
app.Run(new[]
{
"animal", "4", "dog", "12",
});
// Then
registrar.Registrations.ContainsKey(typeof(ICommand)).ShouldBeTrue();
registrar.Registrations[typeof(ICommand)].ShouldContain(typeof(GenericCommand<FooCommandSettings>));
registrar.Registrations[typeof(ICommand)].ShouldContain(typeof(DogCommand));
registrar.Registrations[typeof(ICommand)].ShouldContain(typeof(HorseCommand));
}
[Fact]
public void Should_Register_Default_Command_When_Configuring_Application()
{
// Given
var registrar = new FakeTypeRegistrar();
var app = new CommandApp<DogCommand>(registrar);
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
app.Run(new[]
{
"12", "4",
});
// Then
registrar.Registrations.ContainsKey(typeof(ICommand)).ShouldBeTrue();
registrar.Registrations[typeof(ICommand)].ShouldContain(typeof(DogCommand));
}
[Fact]
public void Can_Register_Default_Command_Settings_When_Configuring_Application()
{
// Given
var registrar = new FakeTypeRegistrar();
registrar.Register(typeof(DogSettings), typeof(DogSettings));
var app = new CommandApp<DogCommand>(registrar);
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
app.Run(new[]
{
"12", "4",
});
// Then
registrar.Registrations.ContainsKey(typeof(DogSettings)).ShouldBeTrue();
registrar.Registrations[typeof(DogSettings)].Count.ShouldBe(1);
registrar.Registrations[typeof(DogSettings)].ShouldContain(typeof(DogSettings));
}
[Fact]
public void Can_Register_Command_Settings_When_Configuring_Application()
{
// Given
var registrar = new FakeTypeRegistrar();
registrar.Register(typeof(DogSettings), typeof(DogSettings));
registrar.Register(typeof(MammalSettings), typeof(MammalSettings));
var app = new CommandApp(registrar);
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<HorseCommand>("horse");
});
});
// When
app.Run(new[]
{
"animal", "4", "dog", "12",
});
// Then
registrar.Registrations.ContainsKey(typeof(DogSettings)).ShouldBeTrue();
registrar.Registrations[typeof(DogSettings)].Count.ShouldBe(1);
registrar.Registrations[typeof(DogSettings)].ShouldContain(typeof(DogSettings));
registrar.Registrations.ContainsKey(typeof(MammalSettings)).ShouldBeTrue();
registrar.Registrations[typeof(MammalSettings)].Count.ShouldBe(1);
registrar.Registrations[typeof(MammalSettings)].ShouldContain(typeof(MammalSettings));
}
[Theory]
[InlineData("true", true)]
[InlineData("True", true)]
[InlineData("false", false)]
[InlineData("False", false)]
public void Should_Accept_Explicit_Boolan_Flag(string value, bool expected)
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<DogCommand>("dog");
});
// When
var result = app.Run(new[]
{
"dog", "12", "4", "--alive", value,
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.IsAlive.ShouldBe(expected);
});
}
[Fact]
public void Should_Throw_When_Encountering_Unknown_Option_In_Strict_Mode()
{
// Given
var app = new CommandApp();
app.Configure(config =>
{
config.PropagateExceptions();
config.UseStrictParsing();
config.AddCommand<DogCommand>("dog");
});
// When
var result = Record.Exception(() => app.Run(new[] { "dog", "--foo" }));
// Then
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe("Unknown option 'foo'.");
});
}
[Fact]
public void Should_Add_Unknown_Option_To_Remaining_Arguments_In_Relaxed_Mode()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
});
});
// When
var result = app.Run(new[]
{
"animal", "4", "dog", "12",
"--foo", "bar",
});
// Then
result.Context.ShouldNotBeNull();
result.Context.Remaining.Parsed.Count.ShouldBe(1);
result.Context.ShouldHaveRemainingArgument("foo", values: new[] { "bar" });
}
[Fact]
public void Should_Add_Unknown_Boolean_Option_To_Remaining_Arguments_In_Relaxed_Mode()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
});
});
// When
var result = app.Run(new[]
{
"animal", "4", "dog", "12", "--foo",
});
// Then
result.Context.ShouldNotBeNull();
result.Context.Remaining.Parsed.Count.ShouldBe(1);
result.Context.ShouldHaveRemainingArgument("foo", values: new[] { (string)null });
}
[Fact]
public void Should_Be_Able_To_Set_The_Default_Command()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<DogCommand>();
// When
var result = app.Run(new[]
{
"4", "12", "--good-boy", "--name", "Rufus",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.Legs.ShouldBe(4);
dog.Age.ShouldBe(12);
dog.GoodBoy.ShouldBe(true);
dog.Name.ShouldBe("Rufus");
});
}
[Fact]
public void Should_Set_Command_Name_In_Context()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
});
});
// When
var result = app.Run(new[]
{
"animal", "4", "dog", "12",
});
// Then
result.Context.ShouldNotBeNull();
result.Context.Name.ShouldBe("dog");
}
[Fact]
public void Should_Pass_Command_Data_In_Context()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog").WithData(123);
});
});
// When
var result = app.Run(new[]
{
"animal", "4", "dog", "12",
});
// Then
result.Context.ShouldNotBeNull();
result.Context.Data.ShouldBe(123);
}
public sealed class Delegate_Commands
{
[Fact]
public void Should_Execute_Delegate_Command_At_Root_Level()
{
// Given
var dog = default(DogSettings);
var data = 0;
var app = new CommandApp();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddDelegate<DogSettings>(
"foo", (context, settings) =>
{
dog = settings;
data = (int)context.Data;
return 1;
}).WithData(2);
});
// When
var result = app.Run(new[] { "foo", "4", "12" });
// Then
result.ShouldBe(1);
dog.ShouldNotBeNull();
dog.Age.ShouldBe(12);
dog.Legs.ShouldBe(4);
data.ShouldBe(2);
}
[Fact]
public void Should_Execute_Nested_Delegate_Command()
{
// Given
var dog = default(DogSettings);
var data = 0;
var app = new CommandApp();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("foo", foo =>
{
foo.AddDelegate<DogSettings>(
"bar", (context, settings) =>
{
dog = settings;
data = (int)context.Data;
return 1;
}).WithData(2);
});
});
// When
var result = app.Run(new[] { "foo", "4", "bar", "12" });
// Then
result.ShouldBe(1);
dog.ShouldNotBeNull();
dog.Age.ShouldBe(12);
dog.Legs.ShouldBe(4);
data.ShouldBe(2);
}
}
public sealed class Remaining_Arguments
{
[Fact]
public void Should_Register_Remaining_Parsed_Arguments_With_Context()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
});
});
// When
var result = app.Run(new[]
{
"animal", "4", "dog", "12", "--",
"--foo", "bar", "--foo", "baz",
"-bar", "\"baz\"", "qux",
});
// Then
result.Context.Remaining.Parsed.Count.ShouldBe(4);
result.Context.ShouldHaveRemainingArgument("foo", values: new[] { "bar", "baz" });
result.Context.ShouldHaveRemainingArgument("b", values: new[] { (string)null });
result.Context.ShouldHaveRemainingArgument("a", values: new[] { (string)null });
result.Context.ShouldHaveRemainingArgument("r", values: new[] { (string)null });
}
[Fact]
public void Should_Register_Remaining_Raw_Arguments_With_Context()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
});
});
// When
var result = app.Run(new[]
{
"animal", "4", "dog", "12", "--",
"--foo", "bar", "-bar", "\"baz\"", "qux",
});
// Then
result.Context.Remaining.Raw.Count.ShouldBe(5);
result.Context.Remaining.Raw[0].ShouldBe("--foo");
result.Context.Remaining.Raw[1].ShouldBe("bar");
result.Context.Remaining.Raw[2].ShouldBe("-bar");
result.Context.Remaining.Raw[3].ShouldBe("baz");
result.Context.Remaining.Raw[4].ShouldBe("qux");
}
}
public sealed class Exception_Handling
{
[Fact]
public void Should_Not_Propagate_Runtime_Exceptions_If_Not_Explicitly_Told_To_Do_So()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<HorseCommand>("horse");
});
});
// When
var result = app.Run(new[] { "animal", "4", "dog", "101", "--name", "Rufus" });
// Then
result.ExitCode.ShouldBe(-1);
}
[Fact]
public void Should_Not_Propagate_Exceptions_If_Not_Explicitly_Told_To_Do_So()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.AddCommand<ThrowingCommand>("throw");
});
// When
var result = app.Run(new[] { "throw" });
// Then
result.ExitCode.ShouldBe(-1);
}
}
}
}

View File

@@ -0,0 +1,23 @@
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class ColorSystemTests
{
[Theory]
[InlineData(ColorSystem.NoColors, ColorSystemSupport.NoColors)]
[InlineData(ColorSystem.Legacy, ColorSystemSupport.Legacy)]
[InlineData(ColorSystem.Standard, ColorSystemSupport.Standard)]
[InlineData(ColorSystem.EightBit, ColorSystemSupport.EightBit)]
[InlineData(ColorSystem.TrueColor, ColorSystemSupport.TrueColor)]
public void Should_Be_Analog_To_ColorSystemSupport(ColorSystem colors, ColorSystemSupport support)
{
// Given, When
var result = (int)colors;
// Then
result.ShouldBe((int)support);
}
}
}

View File

@@ -0,0 +1,287 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class ColorTests
{
public sealed class TheEqualsMethod
{
[Fact]
public void Should_Consider_Color_And_Non_Color_Equal()
{
// Given
var color1 = new Color(128, 0, 128);
// When
var result = color1.Equals("Foo");
// Then
result.ShouldBeFalse();
}
[Fact]
public void Should_Consider_Same_Colors_Equal_By_Component()
{
// Given
var color1 = new Color(128, 0, 128);
var color2 = new Color(128, 0, 128);
// When
var result = color1.Equals(color2);
// Then
result.ShouldBeTrue();
}
[Fact]
public void Should_Consider_Same_Known_Colors_Equal()
{
// Given
var color1 = Color.Cyan1;
var color2 = Color.Cyan1;
// When
var result = color1.Equals(color2);
// Then
result.ShouldBeTrue();
}
[Fact]
public void Should_Consider_Known_Color_And_Color_With_Same_Components_Equal()
{
// Given
var color1 = Color.Cyan1;
var color2 = new Color(0, 255, 255);
// When
var result = color1.Equals(color2);
// Then
result.ShouldBeTrue();
}
[Fact]
public void Should_Not_Consider_Different_Colors_Equal()
{
// Given
var color1 = new Color(128, 0, 128);
var color2 = new Color(128, 128, 128);
// When
var result = color1.Equals(color2);
// Then
result.ShouldBeFalse();
}
[Fact]
public void Shourd_Not_Consider_Black_And_Default_Colors_Equal()
{
// Given
var color1 = Color.Default;
var color2 = Color.Black;
// When
var result = color1.Equals(color2);
// Then
result.ShouldBeFalse();
}
}
public sealed class TheGetHashCodeMethod
{
[Fact]
public void Should_Return_Same_HashCode_For_Same_Colors()
{
// Given
var color1 = new Color(128, 0, 128);
var color2 = new Color(128, 0, 128);
// When
var hash1 = color1.GetHashCode();
var hash2 = color2.GetHashCode();
// Then
hash1.ShouldBe(hash2);
}
[Fact]
public void Should_Return_Different_HashCode_For_Different_Colors()
{
// Given
var color1 = new Color(128, 0, 128);
var color2 = new Color(128, 128, 128);
// When
var hash1 = color1.GetHashCode();
var hash2 = color2.GetHashCode();
// Then
hash1.ShouldNotBe(hash2);
}
}
public sealed class ImplicitConversions
{
public sealed class Int32ToColor
{
public static IEnumerable<object[]> Data =>
Enumerable.Range(0, 255)
.Select(number => new object[] { number });
[Theory]
[MemberData(nameof(Data))]
public void Should_Return_Expected_Color(int number)
{
// Given, When
var result = (Color)number;
// Then
result.ShouldBe(Color.FromInt32(number));
}
[Fact]
public void Should_Throw_If_Integer_Is_Lower_Than_Zero()
{
// Given, When
var result = Record.Exception(() => (Color)(-1));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("Color number must be between 0 and 255");
}
[Fact]
public void Should_Throw_If_Integer_Is_Higher_Than_255()
{
// Given, When
var result = Record.Exception(() => (Color)256);
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("Color number must be between 0 and 255");
}
}
public sealed class ConsoleColorToColor
{
[Theory]
[InlineData(ConsoleColor.Black, 0)]
[InlineData(ConsoleColor.DarkRed, 1)]
[InlineData(ConsoleColor.DarkGreen, 2)]
[InlineData(ConsoleColor.DarkYellow, 3)]
[InlineData(ConsoleColor.DarkBlue, 4)]
[InlineData(ConsoleColor.DarkMagenta, 5)]
[InlineData(ConsoleColor.DarkCyan, 6)]
[InlineData(ConsoleColor.Gray, 7)]
[InlineData(ConsoleColor.DarkGray, 8)]
[InlineData(ConsoleColor.Red, 9)]
[InlineData(ConsoleColor.Green, 10)]
[InlineData(ConsoleColor.Yellow, 11)]
[InlineData(ConsoleColor.Blue, 12)]
[InlineData(ConsoleColor.Magenta, 13)]
[InlineData(ConsoleColor.Cyan, 14)]
[InlineData(ConsoleColor.White, 15)]
public void Should_Return_Expected_Color(ConsoleColor color, int expected)
{
// Given, When
var result = (Color)color;
// Then
result.ShouldBe(Color.FromInt32(expected));
}
}
public sealed class ColorToConsoleColor
{
[Theory]
[InlineData(0, ConsoleColor.Black)]
[InlineData(1, ConsoleColor.DarkRed)]
[InlineData(2, ConsoleColor.DarkGreen)]
[InlineData(3, ConsoleColor.DarkYellow)]
[InlineData(4, ConsoleColor.DarkBlue)]
[InlineData(5, ConsoleColor.DarkMagenta)]
[InlineData(6, ConsoleColor.DarkCyan)]
[InlineData(7, ConsoleColor.Gray)]
[InlineData(8, ConsoleColor.DarkGray)]
[InlineData(9, ConsoleColor.Red)]
[InlineData(10, ConsoleColor.Green)]
[InlineData(11, ConsoleColor.Yellow)]
[InlineData(12, ConsoleColor.Blue)]
[InlineData(13, ConsoleColor.Magenta)]
[InlineData(14, ConsoleColor.Cyan)]
[InlineData(15, ConsoleColor.White)]
public void Should_Return_Expected_ConsoleColor_For_Known_Color(int color, ConsoleColor expected)
{
// Given, When
var result = (ConsoleColor)Color.FromInt32(color);
// Then
result.ShouldBe(expected);
}
}
}
public sealed class TheToMarkupMethod
{
[Fact]
public void Should_Return_Expected_Markup_For_Default_Color()
{
// Given, When
var result = Color.Default.ToMarkup();
// Then
result.ShouldBe("default");
}
[Fact]
public void Should_Return_Expected_Markup_For_Known_Color()
{
// Given, When
var result = Color.Red.ToMarkup();
// Then
result.ShouldBe("red");
}
[Fact]
public void Should_Return_Expected_Markup_For_Custom_Color()
{
// Given, When
var result = new Color(255, 1, 12).ToMarkup();
// Then
result.ShouldBe("#FF010C");
}
}
public sealed class TheToStringMethod
{
[Fact]
public void Should_Return_Color_Name_For_Known_Colors()
{
// Given, When
var name = Color.Fuchsia.ToString();
// Then
name.ShouldBe("fuchsia");
}
[Fact]
public void Should_Return_Hex_String_For_Unknown_Colors()
{
// Given, When
var name = new Color(128, 0, 128).ToString();
// Then
name.ShouldBe("#800080 (RGB=128,0,128)");
}
}
}
}

View File

@@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/Columns")]
public sealed class ColumnsTests
{
private sealed class User
{
public string Name { get; set; }
public string Country { get; set; }
}
[Fact]
[Expectation("Render")]
public Task Should_Render_Columns_Correctly()
{
// Given
var console = new TestConsole().Width(61);
var users = new[]
{
new User { Name = "Savannah Thompson", Country = "Australia" },
new User { Name = "Sophie Ramos", Country = "United States" },
new User { Name = "Katrin Goldberg", Country = "Germany" },
};
var cards = new List<Panel>();
foreach (var user in users)
{
cards.Add(
new Panel($"[b]{user.Name}[/]\n[yellow]{user.Country}[/]")
.RoundedBorder().Expand());
}
// When
console.Write(new Columns(cards));
// Then
return Verifier.Verify(console.Output);
}
}
}

View File

@@ -0,0 +1,96 @@
using Shouldly;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class EmojiTests
{
[Fact]
public void Should_Substitute_Emoji_Shortcodes_In_Markdown()
{
// Given
var console = new TestConsole();
// When
console.Markup("Hello :globe_showing_europe_africa:!");
// Then
console.Output.ShouldBe("Hello 🌍!");
}
[Fact]
public void Should_Contain_Predefined_Emojis()
{
// Given, When
const string result = "Hello " + Emoji.Known.GlobeShowingEuropeAfrica + "!";
// Then
result.ShouldBe("Hello 🌍!");
}
public sealed class TheReplaceMethod
{
[Fact]
public void Should_Replace_Emojis_In_Text()
{
// Given, When
var result = Emoji.Replace("Hello :globe_showing_europe_africa:!");
// Then
result.ShouldBe("Hello 🌍!");
}
}
public sealed class Parsing
{
[Theory]
[InlineData(":", ":")]
[InlineData("::", "::")]
[InlineData(":::", ":::")]
[InlineData("::::", "::::")]
[InlineData("::i:", "::i:")]
[InlineData(":i:i:", ":i:i:")]
[InlineData("::globe_showing_europe_africa::", ":🌍:")]
[InlineData(":globe_showing_europe_africa::globe_showing_europe_africa:", "🌍🌍")]
[InlineData("::globe_showing_europe_africa:::test:::globe_showing_europe_africa:::", ":🌍::test::🌍::")]
public void Can_Handle_Different_Combinations(string markup, string expected)
{
// Given
var console = new TestConsole();
// When
console.Markup(markup);
// Then
console.Output.ShouldBe(expected);
}
[Fact]
public void Should_Leave_Single_Colons()
{
// Given
var console = new TestConsole();
// When
console.Markup("Hello :globe_showing_europe_africa:! Output: good");
// Then
console.Output.ShouldBe("Hello 🌍! Output: good");
}
[Fact]
public void Unknown_emojis_should_remain_unchanged()
{
// Given
var console = new TestConsole();
// When
console.Markup("Hello :globe_showing_flat_earth:!");
// Then
console.Output.ShouldBe("Hello :globe_showing_flat_earth:!");
}
}
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Threading.Tasks;
using Spectre.Console.Testing;
using Spectre.Console.Tests.Data;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Exception")]
public sealed class ExceptionTests
{
[Fact]
[Expectation("Default")]
public Task Should_Write_Exception()
{
// Given
var console = new TestConsole().Width(1024);
var dex = GetException(() => TestExceptions.MethodThatThrows(null));
// When
var result = console.WriteNormalizedException(dex);
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("ShortenedTypes")]
public Task Should_Write_Exception_With_Shortened_Types()
{
// Given
var console = new TestConsole().Width(1024);
var dex = GetException(() => TestExceptions.MethodThatThrows(null));
// When
var result = console.WriteNormalizedException(dex, ExceptionFormats.ShortenTypes);
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("ShortenedMethods")]
public Task Should_Write_Exception_With_Shortened_Methods()
{
// Given
var console = new TestConsole().Width(1024);
var dex = GetException(() => TestExceptions.MethodThatThrows(null));
// When
var result = console.WriteNormalizedException(dex, ExceptionFormats.ShortenMethods);
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("InnerException")]
public Task Should_Write_Exception_With_Inner_Exception()
{
// Given
var console = new TestConsole().Width(1024);
var dex = GetException(() => TestExceptions.ThrowWithInnerException());
// When
var result = console.WriteNormalizedException(dex);
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("CallSite")]
public Task Should_Write_Exceptions_With_Generic_Type_Parameters_In_Callsite_As_Expected()
{
// Given
var console = new TestConsole().Width(1024);
var dex = GetException(() => TestExceptions.ThrowWithGenericInnerException());
// When
var result = console.WriteNormalizedException(dex);
// Then
return Verifier.Verify(result);
}
public static Exception GetException(Action action)
{
try
{
action?.Invoke();
}
catch (Exception e)
{
return e;
}
throw new InvalidOperationException("Exception harness failed");
}
}
}

View File

@@ -0,0 +1,107 @@
using System.Threading.Tasks;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/Figlet")]
public sealed class FigletTests
{
[Fact]
[Expectation("Load_Stream")]
public async Task Should_Load_Font_From_Stream()
{
// Given
var console = new TestConsole().Width(180);
var font = FigletFont.Load(EmbeddedResourceReader.LoadResourceStream("Spectre.Console.Tests/Data/starwars.flf"));
var text = new FigletText(font, "Patrik was here");
// When
console.Write(text);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render")]
public async Task Should_Render_Text_Correctly()
{
// Given
var console = new TestConsole().Width(70);
var text = new FigletText(FigletFont.Default, "Patrik was here");
// When
console.Write(text);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Wrapped")]
public async Task Should_Render_Wrapped_Text_Correctly()
{
// Given
var console = new TestConsole().Width(70);
var text = new FigletText(FigletFont.Default, "Spectre.Console");
// When
console.Write(text);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_LeftAligned")]
public async Task Should_Render_Left_Aligned_Text_Correctly()
{
// Given
var console = new TestConsole().Width(120);
var text = new FigletText(FigletFont.Default, "Spectre.Console")
.Alignment(Justify.Left);
// When
console.Write(text);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Centered")]
public async Task Should_Render_Centered_Text_Correctly()
{
// Given
var console = new TestConsole().Width(120);
var text = new FigletText(FigletFont.Default, "Spectre.Console")
.Alignment(Justify.Center);
// When
console.Write(text);
// Then
await Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_RightAligned")]
public async Task Should_Render_Right_Aligned_Text_Correctly()
{
// Given
var console = new TestConsole().Width(120);
var text = new FigletText(FigletFont.Default, "Spectre.Console")
.Alignment(Justify.Right);
// When
console.Write(text);
// Then
await Verifier.Verify(console.Output);
}
}
}

View File

@@ -0,0 +1,206 @@
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
{
[UsesVerify]
[ExpectationPath("Widgets/Grid")]
public sealed class GridTests
{
public sealed class TheAddColumnMethod
{
[Fact]
public void Should_Throw_If_Rows_Are_Not_Empty()
{
// Given
var grid = new Grid();
grid.AddColumn();
grid.AddRow("Hello World!");
// When
var result = Record.Exception(() => grid.AddColumn());
// Then
result.ShouldBeOfType<InvalidOperationException>()
.Message.ShouldBe("Cannot add new columns to grid with existing rows.");
}
}
public sealed class TheAddRowMethod
{
[Fact]
public void Should_Throw_If_Rows_Are_Null()
{
// Given
var grid = new Grid();
// When
var result = Record.Exception(() => grid.AddRow(null));
// Then
result.ShouldBeOfType<ArgumentNullException>()
.ParamName.ShouldBe("columns");
}
[Fact]
public void Should_Add_Empty_Items_If_User_Provides_Less_Row_Items_Than_Columns()
{
// Given
var grid = new Grid();
grid.AddColumn();
grid.AddColumn();
// When
grid.AddRow("Foo");
// Then
grid.Rows.Count.ShouldBe(1);
}
[Fact]
public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns()
{
// Given
var grid = new Grid();
grid.AddColumn();
// When
var result = Record.Exception(() => grid.AddRow("Foo", "Bar"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("The number of row columns are greater than the number of grid columns.");
}
}
[UsesVerify]
[ExpectationPath("AddEmptyRow")]
public sealed class TheAddEmptyRowMethod
{
[Fact]
[Expectation("Render")]
public Task Should_Add_Empty_Row()
{
// Given
var console = new TestConsole();
var grid = new Grid();
grid.AddColumns(2);
grid.AddRow("Foo", "Bar");
grid.AddEmptyRow();
grid.AddRow("Qux", "Corgi");
grid.AddEmptyRow();
// When
console.Write(grid);
// Then
return Verifier.Verify(console.Output);
}
}
[Fact]
[Expectation("Render")]
public Task Should_Render_Grid_Correctly()
{
// Given
var console = new TestConsole();
var grid = new Grid();
grid.AddColumn();
grid.AddColumn();
grid.AddColumn();
grid.AddRow("Qux", "Corgi", "Waldo");
grid.AddRow("Grault", "Garply", "Fred");
// When
console.Write(grid);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_2")]
public Task Should_Render_Grid_Correctly_2()
{
var console = new TestConsole();
var grid = new Grid();
grid.AddColumn(new GridColumn { NoWrap = true });
grid.AddColumn(new GridColumn { Padding = new Padding(2, 0, 0, 0) });
grid.AddRow("[bold]Options[/]", string.Empty);
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "Show command line help.");
grid.AddRow(" [blue]-c[/], [blue]--configuration[/]", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");
// When
console.Write(grid);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Alignment")]
public Task Should_Render_Grid_Column_Alignment_Correctly()
{
// Given
var console = new TestConsole();
var grid = new Grid();
grid.AddColumn(new GridColumn { Alignment = Justify.Right });
grid.AddColumn(new GridColumn { Alignment = Justify.Center });
grid.AddColumn(new GridColumn { Alignment = Justify.Left });
grid.AddRow("Foo", "Bar", "Baz");
grid.AddRow("Qux", "Corgi", "Waldo");
grid.AddRow("Grault", "Garply", "Fred");
// When
console.Write(grid);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Padding")]
public Task Should_Use_Default_Padding()
{
// Given
var console = new TestConsole();
var grid = new Grid();
grid.AddColumns(3);
grid.AddRow("Foo", "Bar", "Baz");
grid.AddRow("Qux", "Corgi", "Waldo");
grid.AddRow("Grault", "Garply", "Fred");
// When
console.Write(grid);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_ExplicitPadding")]
public Task Should_Render_Explicit_Grid_Column_Padding_Correctly()
{
// Given
var console = new TestConsole();
var grid = new Grid();
grid.AddColumn(new GridColumn { Padding = new Padding(3, 0, 0, 0) });
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0, 0, 0) });
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0, 3, 0) });
grid.AddRow("Foo", "Bar", "Baz");
grid.AddRow("Qux", "Corgi", "Waldo");
grid.AddRow("Grault", "Garply", "Fred");
// When
console.Write(grid);
// Then
return Verifier.Verify(console.Output);
}
}
}

View File

@@ -0,0 +1,143 @@
using System;
using Shouldly;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class MarkupTests
{
public sealed class TheLengthProperty
{
[Theory]
[InlineData("Hello", 5)]
[InlineData("Hello\nWorld", 11)]
[InlineData("[yellow]Hello[/]", 5)]
public void Should_Return_The_Number_Of_Characters(string input, int expected)
{
// Given
var markup = new Markup(input);
// When
var result = markup.Length;
// Then
result.ShouldBe(expected);
}
}
public sealed class TheLinesProperty
{
[Theory]
[InlineData("Hello", 1)]
[InlineData("Hello\nWorld", 2)]
[InlineData("[yellow]Hello[/]\nWorld", 2)]
public void Should_Return_The_Number_Of_Lines(string input, int expected)
{
// Given
var markup = new Markup(input);
// When
var result = markup.Lines;
// Then
result.ShouldBe(expected);
}
}
public sealed class TheEscapeMethod
{
[Theory]
[InlineData("Hello World", "Hello World")]
[InlineData("Hello World [", "Hello World [[")]
[InlineData("Hello World ]", "Hello World ]]")]
[InlineData("Hello [World]", "Hello [[World]]")]
[InlineData("Hello [[World]]", "Hello [[[[World]]]]")]
public void Should_Escape_Markup_As_Expected(string input, string expected)
{
// Given, When
var result = Markup.Escape(input);
// Then
result.ShouldBe(expected);
}
}
public sealed class TheRemoveMethod
{
[Theory]
[InlineData("Hello World", "Hello World")]
[InlineData("Hello [blue]World", "Hello World")]
[InlineData("Hello [blue]World[/]", "Hello World")]
public void Should_Remove_Markup_From_Text(string input, string expected)
{
// Given, When
var result = Markup.Remove(input);
// Then
result.ShouldBe(expected);
}
}
[Theory]
[InlineData("Hello [[ World ]")]
[InlineData("Hello [[ World ] !")]
public void Should_Throw_If_Closing_Tag_Is_Not_Properly_Escaped(string input)
{
// Given
var console = new TestConsole();
// When
var result = Record.Exception(() => new Markup(input));
// Then
result.ShouldNotBeNull();
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("Encountered unescaped ']' token at position 16");
}
[Fact]
public void Should_Escape_Markup_Blocks_As_Expected()
{
// Given
var console = new TestConsole();
var markup = new Markup("Hello [[ World ]] !");
// When
console.Write(markup);
// Then
console.Output.ShouldBe("Hello [ World ] !");
}
[Theory]
[InlineData("Hello [link=http://example.com]example.com[/]", "Hello example.com")]
[InlineData("Hello [link=http://example.com]http://example.com[/]", "Hello http://example.com")]
public void Should_Render_Links_As_Expected(string input, string output)
{
// Given
var console = new TestConsole();
var markup = new Markup(input);
// When
console.Write(markup);
// Then
console.Output.ShouldBe(output);
}
[Fact]
public void Should_Not_Fail_With_Brackets_On_Calls_Without_Args()
{
// Given
var console = new TestConsole();
// When
console.MarkupLine("{");
// Then
console.Output.NormalizeLineEndings()
.ShouldBe("{\n");
}
}
}

View File

@@ -0,0 +1,75 @@
using System.Threading.Tasks;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/Padder")]
public sealed class PadderTests
{
[Fact]
[Expectation("Render")]
public Task Should_Render_Padded_Object_Correctly()
{
// Given
var console = new TestConsole().Width(60);
var table = new Table();
table.AddColumn("Foo");
table.AddColumn("Bar");
table.AddRow("Baz", "Qux");
table.AddRow("Corgi", "Waldo");
// When
console.Write(new Padder(table).Padding(1, 2, 3, 4));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Expanded")]
public Task Should_Render_Expanded_Padded_Object_Correctly()
{
// Given
var console = new TestConsole().Width(60);
var table = new Table();
table.AddColumn("Foo");
table.AddColumn("Bar");
table.AddRow("Baz", "Qux");
table.AddRow("Corgi", "Waldo");
// When
console.Write(new Padder(table)
.Padding(1, 2, 3, 4)
.Expand());
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Nested")]
public Task Should_Render_Padded_Object_Correctly_When_Nested_Within_Other_Object()
{
// Given
var console = new TestConsole().Width(60);
var table = new Table();
table.AddColumn("Foo");
table.AddColumn("Bar", c => c.PadLeft(0).PadRight(0));
table.AddRow("Baz", "Qux");
table.AddRow(new Text("Corgi"), new Padder(new Panel("Waldo"))
.Padding(2, 1));
// When
console.Write(new Padder(table)
.Padding(1, 2, 3, 4)
.Expand());
// Then
return Verifier.Verify(console.Output);
}
}
}

View File

@@ -0,0 +1,309 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Spectre.Console.Rendering;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/Panel")]
public sealed class PanelTests
{
[Fact]
[Expectation("Render")]
public Task Should_Render_Panel()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel(new Text("Hello World")));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_ZeroPadding")]
public Task Should_Render_Panel_With_Padding_Set_To_Zero()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel(new Text("Hello World"))
{
Padding = new Padding(0, 0, 0, 0),
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Padding")]
public Task Should_Render_Panel_With_Padding()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel(new Text("Hello World"))
{
Padding = new Padding(3, 1, 5, 2),
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Header")]
public Task Should_Render_Panel_With_Header()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel("Hello World")
{
Header = new PanelHeader("Greeting"),
Expand = true,
Padding = new Padding(2, 0, 2, 0),
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Header_LeftAligned")]
public Task Should_Render_Panel_With_Left_Aligned_Header()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel("Hello World")
{
Header = new PanelHeader("Greeting").LeftAligned(),
Expand = true,
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Header_Centered")]
public Task Should_Render_Panel_With_Centered_Header()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel("Hello World")
{
Header = new PanelHeader("Greeting").Centered(),
Expand = true,
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Header_RightAligned")]
public Task Should_Render_Panel_With_Right_Aligned_Header()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel("Hello World")
{
Header = new PanelHeader("Greeting").RightAligned(),
Expand = true,
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Header_Collapse")]
public Task Should_Collapse_Header_If_It_Will_Not_Fit()
{
// Given
var console = new TestConsole().Width(10);
// When
console.Write(new Panel("Hello World")
{
Header = new PanelHeader("Greeting"),
Expand = true,
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Unicode")]
public Task Should_Render_Panel_With_Unicode_Correctly()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel(new Text(" \n💩\n ")));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Multiline")]
public Task Should_Render_Panel_With_Multiple_Lines()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel(new Text("Hello World\nFoo Bar")));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_LineEndings")]
public Task Should_Preserve_Explicit_Line_Ending()
{
// Given
var console = new TestConsole();
var text = new Panel(
new Markup("I heard [underline on blue]you[/] like 📦\n\n\n\nSo I put a 📦 in a 📦"));
// When
console.Write(text);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Expand")]
public Task Should_Expand_Panel_If_Enabled()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel(new Text("Hello World"))
{
Expand = true,
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Child_RightAligned")]
public Task Should_Justify_Child_To_Right_Correctly()
{
// Given
var console = new TestConsole().Width(25);
// When
console.Write(
new Panel(new Text("Hello World").RightAligned())
{
Expand = true,
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Child_Centered")]
public Task Should_Center_Child_Correctly()
{
// Given
var console = new TestConsole().Width(25);
// When
console.Write(
new Panel(new Text("Hello World").Centered())
{
Expand = true,
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Child_Panel")]
public Task Should_Render_Panel_Inside_Panel_Correctly()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel(new Panel(new Text("Hello World"))));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Wrap")]
public Task Should_Wrap_Content_Correctly()
{
// Given
var console = new TestConsole().Width(84);
var rows = new List<IRenderable>();
var grid = new Grid();
grid.AddColumn(new GridColumn().PadLeft(2).PadRight(0));
grid.AddColumn(new GridColumn().PadLeft(1).PadRight(0));
grid.AddRow("at", "[grey]System.Runtime.CompilerServices.TaskAwaiter.[/][yellow]HandleNonSuccessAndDebuggerNotification[/]([blue]Task[/] task)");
rows.Add(grid);
var panel = new Panel(grid)
.Expand().RoundedBorder()
.BorderStyle(new Style().Foreground(Color.Grey))
.Header("[grey]Short paths[/]");
// When
console.Write(panel);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_CJK")]
public Task Should_Wrap_Table_With_CJK_Tables_In_Panel_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table();
table.AddColumn("测试");
table.AddRow("测试");
var panel = new Panel(table);
// When
console.Write(panel);
// Then
return Verifier.Verify(console.Output);
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Globalization;
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class DownloadedColumnTests
{
[Theory]
[InlineData(0, 1, "0/1 byte")]
[InlineData(37, 101, "37/101 bytes")]
[InlineData(101, 101, "101 bytes")]
[InlineData(512, 1024, "0.5/1.0 KB")]
[InlineData(1024, 1024, "1.0 KB")]
[InlineData(1024 * 512, 5 * 1024 * 1024, "0.5/5.0 MB")]
[InlineData(5 * 1024 * 1024, 5 * 1024 * 1024, "5.0 MB")]
public void Should_Return_Correct_Value(double value, double total, string expected)
{
// Given
var fixture = new ProgressColumnFixture<DownloadedColumn>(value, total);
fixture.Column.Culture = CultureInfo.InvariantCulture;
// When
var result = fixture.Render();
// Then
result.ShouldBe(expected);
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using Spectre.Console.Rendering;
using Spectre.Console.Testing;
namespace Spectre.Console.Tests.Unit
{
public sealed class ProgressColumnFixture<T>
where T : ProgressColumn, new()
{
public T Column { get; }
public ProgressTask Task { get; set; }
public ProgressColumnFixture(double completed, double total)
{
Column = new T();
Task = new ProgressTask(1, "Foo", total);
Task.Increment(completed);
}
public string Render()
{
var console = new TestConsole();
var context = new RenderContext(console.Profile.Capabilities);
console.Write(Column.Render(context, Task, TimeSpan.Zero));
return console.Output;
}
}
}

View File

@@ -0,0 +1,271 @@
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
{
[UsesVerify]
[ExpectationPath("Widgets/Progress")]
public sealed class ProgressTests
{
[Fact]
public void Should_Render_Task_Correctly()
{
// Given
var console = new TestConsole()
.Width(10)
.Interactive()
.EmitAnsiSequences();
var progress = new Progress(console)
.Columns(new[] { new ProgressBarColumn() })
.AutoRefresh(false)
.AutoClear(true);
// When
progress.Start(ctx => ctx.AddTask("foo"));
// Then
console.Output
.NormalizeLineEndings()
.ShouldBe(
"[?25l" + // Hide cursor
" \n" + // Top padding
"━━━━━━━━━━\n" + // Task
" " + // Bottom padding
"[?25h"); // Clear + show cursor
}
[Fact]
public void Should_Not_Auto_Clear_If_Specified()
{
// Given
var console = new TestConsole()
.Width(10)
.Interactive()
.EmitAnsiSequences();
var progress = new Progress(console)
.Columns(new[] { new ProgressBarColumn() })
.AutoRefresh(false)
.AutoClear(false);
// When
progress.Start(ctx => ctx.AddTask("foo"));
// Then
console.Output
.NormalizeLineEndings()
.ShouldBe(
"[?25l" + // Hide cursor
" \n" + // Top padding
"━━━━━━━━━━\n" + // Task
" \n" + // Bottom padding
"[?25h"); // show cursor
}
[Fact]
[Expectation("Render_ReduceWidth")]
public Task Should_Reduce_Width_If_Needed()
{
// Given
var console = new TestConsole()
.Width(20)
.Interactive();
var progress = new Progress(console)
.Columns(new ProgressColumn[]
{
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
new RemainingTimeColumn(),
new SpinnerColumn(),
})
.AutoRefresh(false)
.AutoClear(false);
// When
progress.Start(ctx =>
{
ctx.AddTask("foo");
ctx.AddTask("bar");
ctx.AddTask("baz");
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
public void Setting_Max_Value_Should_Set_The_MaxValue_And_Cap_Value()
{
// Given
var console = new TestConsole()
.Interactive();
var task = default(ProgressTask);
var progress = new Progress(console)
.Columns(new[] { new ProgressBarColumn() })
.AutoRefresh(false)
.AutoClear(false);
// When
progress.Start(ctx =>
{
task = ctx.AddTask("foo");
task.Increment(100);
task.MaxValue = 20;
});
// Then
task.MaxValue.ShouldBe(20);
task.Value.ShouldBe(20);
}
[Fact]
public void Setting_Value_Should_Override_Incremented_Value()
{
// Given
var console = new TestConsole()
.Interactive();
var task = default(ProgressTask);
var progress = new Progress(console)
.Columns(new[] { new ProgressBarColumn() })
.AutoRefresh(false)
.AutoClear(false);
// When
progress.Start(ctx =>
{
task = ctx.AddTask("foo");
task.Increment(50);
task.Value = 20;
});
// Then
task.MaxValue.ShouldBe(100);
task.Value.ShouldBe(20);
}
[Fact]
public void Setting_Value_To_MaxValue_Should_Finish_Task()
{
// Given
var console = new TestConsole()
.Interactive();
var task = default(ProgressTask);
var progress = new Progress(console)
.Columns(new[] { new ProgressBarColumn() })
.AutoRefresh(false)
.AutoClear(false);
// When
progress.Start(ctx =>
{
task = ctx.AddTask("foo");
task.Value = task.MaxValue;
});
// Then
task.IsFinished.ShouldBe(true);
}
[Fact]
public void Should_Increment_Manually_Set_Value()
{
// Given
var console = new TestConsole()
.Interactive();
var task = default(ProgressTask);
var progress = new Progress(console)
.Columns(new[] { new ProgressBarColumn() })
.AutoRefresh(false)
.AutoClear(false);
// When
progress.Start(ctx =>
{
task = ctx.AddTask("foo");
task.Value = 50;
task.Increment(10);
});
// Then
task.Value.ShouldBe(60);
}
[Fact]
public void Should_Hide_Completed_Tasks()
{
// Given
var console = new TestConsole()
.Width(10)
.Interactive()
.EmitAnsiSequences();
var taskFinished = default(ProgressTask);
var taskInProgress1 = default(ProgressTask);
var taskInProgress2 = default(ProgressTask);
var progress = new Progress(console)
.Columns(new[] { new ProgressBarColumn() })
.AutoRefresh(false)
.AutoClear(false)
.HideCompleted(true);
// When
progress.Start(ctx =>
{
taskInProgress1 = ctx.AddTask("foo");
taskFinished = ctx.AddTask("bar");
taskInProgress2 = ctx.AddTask("baz");
taskInProgress2.Increment(20);
taskFinished.Value = taskFinished.MaxValue;
});
// Then
console.Output
.NormalizeLineEndings()
.ShouldBe(
"[?25l" + // Hide cursor
" \n" + // top padding
"━━━━━━━━━━\n" + // taskInProgress1
"━━━━━━━━━━\n" + // taskInProgress2
" \n" + // bottom padding
"[?25h"); // show cursor
}
[Fact]
public void Should_Report_Max_Remaining_Time_For_Extremely_Small_Progress()
{
// Given
var console = new TestConsole()
.Interactive();
var task = default(ProgressTask);
var progress = new Progress(console)
.Columns(new[] { new RemainingTimeColumn() })
.AutoRefresh(false)
.AutoClear(false);
// When
progress.Start(ctx =>
{
task = ctx.AddTask("foo");
task.Increment(double.Epsilon);
task.Increment(double.Epsilon);
});
// Then
task.RemainingTime.ShouldBe(TimeSpan.MaxValue);
}
}
}

View File

@@ -0,0 +1,54 @@
using System.Threading.Tasks;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/Recorder")]
public sealed class RecorderTests
{
[Fact]
[Expectation("Text")]
public Task Should_Export_Text_As_Expected()
{
// Given
var console = new TestConsole();
var recorder = new Recorder(console);
recorder.Write(new Table()
.AddColumns("Foo", "Bar", "Qux")
.AddRow("Corgi", "Waldo", "Zap")
.AddRow(new Panel("Hello World").RoundedBorder()));
// When
var result = recorder.ExportText();
// Then
return Verifier.Verify(result);
}
[Fact]
[Expectation("Html")]
public Task Should_Export_Html_Text_As_Expected()
{
// Given
var console = new TestConsole();
var recorder = new Recorder(console);
recorder.Write(new Table()
.AddColumns("[red on black]Foo[/]", "[green bold]Bar[/]", "[blue italic]Qux[/]")
.AddRow("[invert underline]Corgi[/]", "[bold strikethrough]Waldo[/]", "[dim]Zap[/]")
.AddRow(new Panel("[blue]Hello World[/]")
.BorderColor(Color.Red).RoundedBorder()));
// When
var result = recorder.ExportHtml();
// Then
return Verifier.Verify(result);
}
}
}

View File

@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using Shouldly;
using Spectre.Console.Rendering;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class RenderHookTests
{
private sealed class HelloRenderHook : IRenderHook
{
public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
{
return new IRenderable[] { new Text("Hello\n") }.Concat(renderables);
}
}
[Fact]
public void Should_Inject_Renderable_Before_Writing_To_Console()
{
// Given
var console = new TestConsole();
console.Pipeline.Attach(new HelloRenderHook());
// When
console.Write(new Text("World"));
// Then
console.Lines[0].ShouldBe("Hello");
console.Lines[1].ShouldBe("World");
}
}
}

View File

@@ -0,0 +1,83 @@
using System.Threading.Tasks;
using Spectre.Console.Rendering;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/Rows")]
public sealed class RowsTests
{
[Fact]
[Expectation("Render")]
public Task Should_Render_Rows()
{
// Given
var console = new TestConsole().Width(60);
var rows = new Rows(
new IRenderable[]
{
new Markup("Hello"),
new Table()
.AddColumns("Foo", "Bar")
.AddRow("Baz", "Qux"),
new Markup("World"),
});
// When
console.Write(rows);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Nested")]
public Task Should_Render_Rows_Correctly_Inside_Other_Widget()
{
// Given
var console = new TestConsole().Width(60);
var table = new Table()
.AddColumns("Foo", "Bar")
.AddRow("HELLO WORLD")
.AddRow(
new Rows(new IRenderable[]
{
new Markup("Hello"),
new Markup("World"),
}), new Text("Qux"));
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Expanded_And_Nested")]
public Task Should_Render_Rows_Correctly_Inside_Other_Widget_When_Expanded()
{
// Given
var console = new TestConsole().Width(60);
var table = new Table()
.AddColumns("Foo", "Bar")
.AddRow("HELLO WORLD")
.AddRow(
new Rows(new IRenderable[]
{
new Markup("Hello"),
new Markup("World"),
}).Expand(), new Text("Qux"));
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
}

View File

@@ -0,0 +1,158 @@
using System.Threading.Tasks;
using Shouldly;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/Rule")]
public sealed class RuleTests
{
[Fact]
[Expectation("Render")]
public Task Should_Render_Default_Rule_Without_Title()
{
// Given
var console = new TestConsole().Width(40);
// When
console.Write(new Rule());
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Border_NoHeader")]
public Task Should_Render_Default_Rule_With_Specified_Border()
{
// Given
var console = new TestConsole().Width(40);
// When
console.Write(new Rule().DoubleBorder());
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Border_Header")]
public Task Should_Render_With_Specified_Box()
{
// Given
var console = new TestConsole().Width(40);
// When
console.Write(new Rule("Hello World").DoubleBorder());
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Header_DefaultAlignment")]
public Task Should_Render_Default_Rule_With_Title_Centered_By_Default()
{
// Given
var console = new TestConsole().Width(40);
// When
console.Write(new Rule("Hello World"));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Header_LeftAligned")]
public Task Should_Render_Default_Rule_With_Title_Left_Aligned()
{
// Given
var console = new TestConsole().Width(40);
// When
console.Write(new Rule("Hello World")
{
Alignment = Justify.Left,
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Header_RightAligned")]
public Task Should_Render_Default_Rule_With_Title_Right_Aligned()
{
// Given
var console = new TestConsole().Width(40);
// When
console.Write(new Rule("Hello World")
{
Alignment = Justify.Right,
});
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Linebreaks")]
public Task Should_Convert_Line_Breaks_In_Title_To_Spaces()
{
// Given
var console = new TestConsole().Width(40);
// When
console.Write(new Rule("Hello\nWorld\r\n!"));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Truncate")]
public Task Should_Truncate_Title()
{
// Given
var console = new TestConsole().Width(40);
// When
console.Write(new Rule(" Hello World "));
// Then
return Verifier.Verify(console.Output);
}
[Theory]
[InlineData(1, "Hello World Hello World Hello World Hello World Hello World", "─")]
[InlineData(2, "Hello World Hello World Hello World Hello World Hello World", "──")]
[InlineData(3, "Hello World Hello World Hello World Hello World Hello World", "───")]
[InlineData(4, "Hello World Hello World Hello World Hello World Hello World", "────")]
[InlineData(5, "Hello World Hello World Hello World Hello World Hello World", "─────")]
[InlineData(6, "Hello World Hello World Hello World Hello World Hello World", "──────")]
[InlineData(7, "Hello World Hello World Hello World Hello World Hello World", "───────")]
[InlineData(8, "Hello World Hello World Hello World Hello World Hello World", "── H… ──")]
[InlineData(8, "A", "── A ───")]
[InlineData(8, "AB", "── AB ──")]
[InlineData(8, "ABC", "── A… ──")]
[InlineData(40, "Hello World Hello World Hello World Hello World Hello World", "──── Hello World Hello World Hello… ────")]
public void Should_Truncate_Too_Long_Title(int width, string input, string expected)
{
// Given
var console = new TestConsole().Width(width);
// When
console.Write(new Rule(input));
// Then
console.Lines.Count.ShouldBe(1);
console.Lines[0].ShouldBe(expected);
}
}
}

View File

@@ -0,0 +1,149 @@
using Shouldly;
using Spectre.Console.Rendering;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class SegmentTests
{
[UsesVerify]
public sealed class TheSplitMethod
{
[Theory]
[InlineData("Foo Bar", 0, "", "Foo Bar")]
[InlineData("Foo Bar", 1, "F", "oo Bar")]
[InlineData("Foo Bar", 2, "Fo", "o Bar")]
[InlineData("Foo Bar", 3, "Foo", " Bar")]
[InlineData("Foo Bar", 4, "Foo ", "Bar")]
[InlineData("Foo Bar", 5, "Foo B", "ar")]
[InlineData("Foo Bar", 6, "Foo Ba", "r")]
[InlineData("Foo Bar", 7, "Foo Bar", null)]
[InlineData("Foo 测试 Bar", 0, "", "Foo 测试 Bar")]
[InlineData("Foo 测试 Bar", 1, "F", "oo 测试 Bar")]
[InlineData("Foo 测试 Bar", 2, "Fo", "o 测试 Bar")]
[InlineData("Foo 测试 Bar", 3, "Foo", " 测试 Bar")]
[InlineData("Foo 测试 Bar", 4, "Foo ", "测试 Bar")]
[InlineData("Foo 测试 Bar", 5, "Foo 测", "试 Bar")]
[InlineData("Foo 测试 Bar", 6, "Foo 测", "试 Bar")]
[InlineData("Foo 测试 Bar", 7, "Foo 测试", " Bar")]
[InlineData("Foo 测试 Bar", 8, "Foo 测试", " Bar")]
[InlineData("Foo 测试 Bar", 9, "Foo 测试 ", "Bar")]
[InlineData("Foo 测试 Bar", 10, "Foo 测试 B", "ar")]
[InlineData("Foo 测试 Bar", 11, "Foo 测试 Ba", "r")]
[InlineData("Foo 测试 Bar", 12, "Foo 测试 Bar", null)]
public void Should_Split_Segment_Correctly(string text, int offset, string expectedFirst, string expectedSecond)
{
// Given
var style = new Style(Color.Red, Color.Green, Decoration.Bold);
var segment = new Segment(text, style);
// When
var (first, second) = segment.Split(offset);
// Then
first.Text.ShouldBe(expectedFirst);
first.Style.ShouldBe(style);
second?.Text?.ShouldBe(expectedSecond);
second?.Style?.ShouldBe(style);
}
}
[UsesVerify]
public sealed class TheSplitLinesMethod
{
[Fact]
public void Should_Split_Segment()
{
// Given, When
var lines = Segment.SplitLines(
new[]
{
new Segment("Foo"),
new Segment("Bar"),
new Segment("\n"),
new Segment("Baz"),
new Segment("Qux"),
new Segment("\n"),
new Segment("Corgi"),
});
// Then
lines.Count.ShouldBe(3);
lines[0].Count.ShouldBe(2);
lines[0][0].Text.ShouldBe("Foo");
lines[0][1].Text.ShouldBe("Bar");
lines[1].Count.ShouldBe(2);
lines[1][0].Text.ShouldBe("Baz");
lines[1][1].Text.ShouldBe("Qux");
lines[2].Count.ShouldBe(1);
lines[2][0].Text.ShouldBe("Corgi");
}
[Fact]
public void Should_Split_Segment_With_Windows_LineBreak()
{
// Given, When
var lines = Segment.SplitLines(
new[]
{
new Segment("Foo"),
new Segment("Bar"),
new Segment("\r\n"),
new Segment("Baz"),
new Segment("Qux"),
new Segment("\r\n"),
new Segment("Corgi"),
});
// Then
lines.Count.ShouldBe(3);
lines[0].Count.ShouldBe(2);
lines[0][0].Text.ShouldBe("Foo");
lines[0][1].Text.ShouldBe("Bar");
lines[1].Count.ShouldBe(2);
lines[1][0].Text.ShouldBe("Baz");
lines[1][1].Text.ShouldBe("Qux");
lines[2].Count.ShouldBe(1);
lines[2][0].Text.ShouldBe("Corgi");
}
[Fact]
public void Should_Split_Segments_With_Linebreak_In_Text()
{
// Given, Given
var lines = Segment.SplitLines(
new[]
{
new Segment("Foo\n"),
new Segment("Bar\n"),
new Segment("Baz"),
new Segment("Qux\n"),
new Segment("Corgi"),
});
// Then
lines.Count.ShouldBe(4);
lines[0].Count.ShouldBe(1);
lines[0][0].Text.ShouldBe("Foo");
lines[1].Count.ShouldBe(1);
lines[1][0].Text.ShouldBe("Bar");
lines[2].Count.ShouldBe(2);
lines[2][0].Text.ShouldBe("Baz");
lines[2][1].Text.ShouldBe("Qux");
lines[3].Count.ShouldBe(1);
lines[3][0].Text.ShouldBe("Corgi");
}
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/Status")]
public sealed class StatusTests
{
public sealed class DummySpinner1 : Spinner
{
public override TimeSpan Interval => TimeSpan.FromMilliseconds(100);
public override bool IsUnicode => true;
public override IReadOnlyList<string> Frames => new List<string> { "*", };
}
public sealed class DummySpinner2 : Spinner
{
public override TimeSpan Interval => TimeSpan.FromMilliseconds(100);
public override bool IsUnicode => true;
public override IReadOnlyList<string> Frames => new List<string> { "-", };
}
[Fact]
[Expectation("Render")]
public Task Should_Render_Status_Correctly()
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.TrueColor)
.Width(10)
.Interactive()
.EmitAnsiSequences();
var status = new Status(console)
{
AutoRefresh = false,
Spinner = new DummySpinner1(),
};
// When
status.Start("foo", ctx =>
{
ctx.Refresh();
ctx.Spinner(new DummySpinner2());
ctx.Status("bar");
ctx.Refresh();
ctx.Spinner(new DummySpinner1());
ctx.Status("baz");
});
// Then
return Verifier.Verify(console.Output);
}
}
}

View File

@@ -0,0 +1,376 @@
using System;
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class StyleTests
{
[Fact]
public void Should_Combine_Two_Styles_As_Expected()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic);
var other = new Style(Color.Green, Color.Silver, Decoration.Underline, "https://example.com");
// When
var result = first.Combine(other);
// Then
result.Foreground.ShouldBe(Color.Green);
result.Background.ShouldBe(Color.Silver);
result.Decoration.ShouldBe(Decoration.Bold | Decoration.Italic | Decoration.Underline);
result.Link.ShouldBe("https://example.com");
}
[Fact]
public void Should_Consider_Two_Identical_Styles_Equal()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
var second = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
// When
var result = first.Equals(second);
// Then
result.ShouldBeTrue();
}
[Fact]
public void Should_Not_Consider_Two_Styles_With_Different_Foreground_Colors_Equal()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
var second = new Style(Color.Blue, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
// When
var result = first.Equals(second);
// Then
result.ShouldBeFalse();
}
[Fact]
public void Should_Not_Consider_Two_Styles_With_Different_Background_Colors_Equal()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
var second = new Style(Color.White, Color.Blue, Decoration.Bold | Decoration.Italic, "http://example.com");
// When
var result = first.Equals(second);
// Then
result.ShouldBeFalse();
}
[Fact]
public void Should_Not_Consider_Two_Styles_With_Different_Decorations_Equal()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
var second = new Style(Color.White, Color.Yellow, Decoration.Bold, "http://example.com");
// When
var result = first.Equals(second);
// Then
result.ShouldBeFalse();
}
[Fact]
public void Should_Not_Consider_Two_Styles_With_Different_Links_Equal()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
var second = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://foo.com");
// When
var result = first.Equals(second);
// Then
result.ShouldBeFalse();
}
public sealed class TheParseMethod
{
[Fact]
public void Default_Keyword_Should_Return_Default_Style()
{
// Given, When
var result = Style.Parse("default");
// Then
result.ShouldNotBeNull();
result.Foreground.ShouldBe(Color.Default);
result.Background.ShouldBe(Color.Default);
result.Decoration.ShouldBe(Decoration.None);
}
[Theory]
[InlineData("bold", Decoration.Bold)]
[InlineData("b", Decoration.Bold)]
[InlineData("dim", Decoration.Dim)]
[InlineData("i", Decoration.Italic)]
[InlineData("italic", Decoration.Italic)]
[InlineData("underline", Decoration.Underline)]
[InlineData("u", Decoration.Underline)]
[InlineData("invert", Decoration.Invert)]
[InlineData("conceal", Decoration.Conceal)]
[InlineData("slowblink", Decoration.SlowBlink)]
[InlineData("rapidblink", Decoration.RapidBlink)]
[InlineData("strikethrough", Decoration.Strikethrough)]
[InlineData("s", Decoration.Strikethrough)]
public void Should_Parse_Decoration(string text, Decoration decoration)
{
// Given, When
var result = Style.Parse(text);
// Then
result.ShouldNotBeNull();
result.Decoration.ShouldBe(decoration);
}
[Fact]
public void Should_Parse_Link_Without_Address()
{
// Given, When
var result = Style.Parse("link");
// Then
result.ShouldNotBeNull();
result.Link.ShouldBe("https://emptylink");
}
[Fact]
public void Should_Parse_Link()
{
// Given, When
var result = Style.Parse("link=https://example.com");
// Then
result.ShouldNotBeNull();
result.Link.ShouldBe("https://example.com");
}
[Fact]
public void Should_Throw_If_Link_Is_Set_Twice()
{
// Given, When
var result = Record.Exception(() => Style.Parse("link=https://example.com link=https://example.com"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("A link has already been set.");
}
[Fact]
public void Should_Parse_Background_If_Foreground_Is_Set_To_Default()
{
// Given, When
var result = Style.Parse("default on green");
// Then
result.ShouldNotBeNull();
result.Decoration.ShouldBe(Decoration.None);
result.Foreground.ShouldBe(Color.Default);
result.Background.ShouldBe(Color.Green);
}
[Fact]
public void Should_Throw_If_Foreground_Is_Set_Twice()
{
// Given, When
var result = Record.Exception(() => Style.Parse("green yellow"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("A foreground color has already been set.");
}
[Fact]
public void Should_Throw_If_Background_Is_Set_Twice()
{
// Given, When
var result = Record.Exception(() => Style.Parse("green on blue yellow"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("A background color has already been set.");
}
[Fact]
public void Should_Throw_If_Color_Name_Could_Not_Be_Found()
{
// Given, When
var result = Record.Exception(() => Style.Parse("bold lol"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("Could not find color or style 'lol'.");
}
[Fact]
public void Should_Throw_If_Background_Color_Name_Could_Not_Be_Found()
{
// Given, When
var result = Record.Exception(() => Style.Parse("blue on lol"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("Could not find color 'lol'.");
}
[Fact]
public void Should_Parse_Colors_And_Decoration_And_Link()
{
// Given, When
var result = Style.Parse("link=https://example.com bold underline blue on green");
// Then
result.ShouldNotBeNull();
result.Decoration.ShouldBe(Decoration.Bold | Decoration.Underline);
result.Foreground.ShouldBe(Color.Blue);
result.Background.ShouldBe(Color.Green);
result.Link.ShouldBe("https://example.com");
}
[Theory]
[InlineData("#FF0000 on #0000FF")]
[InlineData("#F00 on #00F")]
public void Should_Parse_Hex_Colors_Correctly(string style)
{
// Given, When
var result = Style.Parse(style);
// Then
result.Foreground.ShouldBe(Color.Red);
result.Background.ShouldBe(Color.Blue);
}
[Theory]
[InlineData("#", "Invalid hex color '#'.")]
[InlineData("#FF00FF00FF", "Invalid hex color '#FF00FF00FF'.")]
[InlineData("#FOO", "Invalid hex color '#FOO'. Could not find any recognizable digits.")]
public void Should_Return_Error_If_Hex_Color_Is_Invalid(string style, string expected)
{
// Given, When
var result = Record.Exception(() => Style.Parse(style));
// Then
result.ShouldNotBeNull();
result.Message.ShouldBe(expected);
}
[Theory]
[InlineData("rgb(255,0,0) on rgb(0,0,255)")]
public void Should_Parse_Rgb_Colors_Correctly(string style)
{
// Given, When
var result = Style.Parse(style);
// Then
result.Foreground.ShouldBe(Color.Red);
result.Background.ShouldBe(Color.Blue);
}
[Theory]
[InlineData("rgb()", "Invalid RGB color 'rgb()'.")]
[InlineData("rgb(", "Invalid RGB color 'rgb('.")]
[InlineData("rgb(255)", "Invalid RGB color 'rgb(255)'.")]
[InlineData("rgb(255,255)", "Invalid RGB color 'rgb(255,255)'.")]
[InlineData("rgb(255,255,255", "Invalid RGB color 'rgb(255,255,255'.")]
[InlineData("rgb(A,B,C)", "Invalid RGB color 'rgb(A,B,C)'. Input string was not in a correct format.")]
public void Should_Return_Error_If_Rgb_Color_Is_Invalid(string style, string expected)
{
// Given, When
var result = Record.Exception(() => Style.Parse(style));
// Then
result.ShouldNotBeNull();
result.Message.ShouldBe(expected);
}
}
public sealed class TheTryParseMethod
{
[Fact]
public void Should_Return_True_If_Parsing_Succeeded()
{
// Given, When
var result = Style.TryParse("bold", out var style);
// Then
result.ShouldBeTrue();
style.ShouldNotBeNull();
style.Decoration.ShouldBe(Decoration.Bold);
}
[Fact]
public void Should_Return_False_If_Parsing_Failed()
{
// Given, When
var result = Style.TryParse("lol", out _);
// Then
result.ShouldBeFalse();
}
}
public sealed class TheToMarkupMethod
{
[Fact]
public void Should_Return_Expected_Markup_For_Style_With_Foreground_Color()
{
// Given
var style = new Style(Color.Red);
// When
var result = style.ToMarkup();
// Then
result.ShouldBe("red");
}
[Fact]
public void Should_Return_Expected_Markup_For_Style_With_Foreground_And_Background_Color()
{
// Given
var style = new Style(Color.Red, Color.Green);
// When
var result = style.ToMarkup();
// Then
result.ShouldBe("red on green");
}
[Fact]
public void Should_Return_Expected_Markup_For_Style_With_Foreground_And_Background_Color_And_Decoration()
{
// Given
var style = new Style(Color.Red, Color.Green, Decoration.Bold | Decoration.Underline);
// When
var result = style.ToMarkup();
// Then
result.ShouldBe("bold underline red on green");
}
[Fact]
public void Should_Return_Expected_Markup_For_Style_With_Only_Background_Color()
{
// Given
var style = new Style(background: Color.Green);
// When
var result = style.ToMarkup();
// Then
result.ShouldBe("default on green");
}
}
}
}

View File

@@ -0,0 +1,829 @@
using System.Threading.Tasks;
using Shouldly;
using Spectre.Console.Rendering;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Borders/Table")]
public sealed class TableBorderTests
{
[UsesVerify]
public sealed class NoBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.None.Visible;
// Then
visibility.ShouldBeFalse();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.None.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.None);
}
}
[Fact]
[Expectation("NoBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().NoBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class AsciiBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.Ascii.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.Ascii.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Ascii);
}
}
[Fact]
[Expectation("AsciiBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().AsciiBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class Ascii2Border
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.Ascii2.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.Ascii2.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Ascii2);
}
}
[Fact]
[Expectation("Ascii2Border")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().Ascii2Border();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class AsciiDoubleHeadBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.AsciiDoubleHead.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.AsciiDoubleHead.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.AsciiDoubleHead);
}
}
[Fact]
[Expectation("AsciiDoubleHeadBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().AsciiDoubleHeadBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class SquareBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.Square.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.Square.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Square);
}
}
[Fact]
[Expectation("SquareBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().SquareBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class RoundedBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.Rounded.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.Rounded.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Square);
}
}
[Fact]
[Expectation("RoundedBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().RoundedBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class MinimalBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.Minimal.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.Minimal.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Minimal);
}
}
[Fact]
[Expectation("MinimalBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().MinimalBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class MinimalHeavyHeadBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.MinimalHeavyHead.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.MinimalHeavyHead.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Minimal);
}
}
[Fact]
[Expectation("MinimalHeavyHeadBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().MinimalHeavyHeadBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class MinimalDoubleHeadBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.MinimalDoubleHead.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.MinimalDoubleHead.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.MinimalDoubleHead);
}
}
[Fact]
[Expectation("MinimalDoubleHeadBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().MinimalDoubleHeadBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class SimpleBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.Simple.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.Simple.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Simple);
}
}
[Fact]
[Expectation("SimpleBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().SimpleBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class HorizontalBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.Horizontal.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.Horizontal.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Horizontal);
}
}
[Fact]
[Expectation("HorizontalBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().HorizontalBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class SimpleHeavyBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.SimpleHeavy.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.SimpleHeavy.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Simple);
}
}
[Fact]
[Expectation("SimpleHeavyBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().SimpleHeavyBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class HeavyBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.Heavy.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.Heavy.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Square);
}
}
[Fact]
[Expectation("HeavyBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().HeavyBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class HeavyEdgeBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.HeavyEdge.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.HeavyEdge.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Square);
}
}
[Fact]
[Expectation("HeavyEdgeBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().HeavyEdgeBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class HeavyHeadBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.HeavyHead.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.HeavyHead.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Square);
}
}
[Fact]
[Expectation("HeavyHeadBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().HeavyHeadBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class DoubleBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.Double.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.Double.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Double);
}
}
[Fact]
[Expectation("DoubleBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().DoubleBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class DoubleEdgeBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.DoubleEdge.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.DoubleEdge.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.DoubleEdge);
}
}
[Fact]
[Expectation("DoubleEdgeBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().DoubleEdgeBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[UsesVerify]
public sealed class MarkdownBorder
{
[Fact]
public void Should_Return_Correct_Visibility()
{
// Given, When
var visibility = TableBorder.Markdown.Visible;
// Then
visibility.ShouldBeTrue();
}
public sealed class TheSafeGetBorderMethod
{
[Fact]
public void Should_Return_Safe_Border()
{
// Given, When
var border = TableBorder.Markdown.GetSafeBorder(safe: true);
// Then
border.ShouldBeSameAs(TableBorder.Markdown);
}
}
[Fact]
[Expectation("MarkdownBorder")]
public Task Should_Render_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable().MarkdownBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("MarkdownBorder_LeftAligned")]
public Task Should_Render_Left_Aligned_Table_Columns_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable(header2: Justify.Left).MarkdownBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("MarkdownBorder_Centered")]
public Task Should_Render_Center_Aligned_Table_Columns_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable(header2: Justify.Center).MarkdownBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("MarkdownBorder_RightAligned")]
public Task Should_Render_Right_Aligned_Table_Columns_As_Expected()
{
// Given
var console = new TestConsole();
var table = Fixture.GetTable(header2: Justify.Right).MarkdownBorder();
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
private static class Fixture
{
public static Table GetTable(Justify? header1 = null, Justify? header2 = null)
{
var table = new Table();
table.AddColumn("Header 1", c => c.Alignment(header1).Footer("Footer 1"));
table.AddColumn("Header 2", c => c.Alignment(header2).Footer("Footer 2"));
table.AddRow("Cell", "Cell");
table.AddRow("Cell", "Cell");
return table;
}
}
}
}

View File

@@ -0,0 +1,501 @@
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
{
[UsesVerify]
[ExpectationPath("Widgets/Table")]
public sealed class TableTests
{
public sealed class TheAddColumnMethod
{
[Fact]
public void Should_Throw_If_Column_Is_Null()
{
// Given
var table = new Table();
// When
var result = Record.Exception(() => table.AddColumn((string)null));
// Then
result.ShouldBeOfType<ArgumentNullException>()
.ParamName.ShouldBe("column");
}
[Fact]
public void Should_Throw_If_Rows_Are_Not_Empty()
{
// Given
var grid = new Table();
grid.AddColumn("Foo");
grid.AddRow("Hello World");
// When
var result = Record.Exception(() => grid.AddColumn("Bar"));
// Then
result.ShouldBeOfType<InvalidOperationException>()
.Message.ShouldBe("Cannot add new columns to table with existing rows.");
}
}
public sealed class TheAddColumnsMethod
{
[Fact]
public void Should_Throw_If_Columns_Are_Null()
{
// Given
var table = new Table();
// When
var result = Record.Exception(() => table.AddColumns((string[])null));
// Then
result.ShouldBeOfType<ArgumentNullException>()
.ParamName.ShouldBe("columns");
}
}
public sealed class TheAddRowMethod
{
[Fact]
public void Should_Throw_If_String_Rows_Are_Null()
{
// Given
var table = new Table();
// When
var result = Record.Exception(() => table.AddRow((string[])null));
// Then
result.ShouldBeOfType<ArgumentNullException>()
.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<ArgumentNullException>()
.ParamName.ShouldBe("columns");
}
[Fact]
public void Should_Add_Empty_Items_If_User_Provides_Less_Row_Items_Than_Columns()
{
// Given
var table = new Table();
table.AddColumn("Hello");
table.AddColumn("World");
// When
table.AddRow("Foo");
// Then
table.Rows.Count.ShouldBe(1);
}
[Fact]
public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns()
{
// Given
var table = new Table();
table.AddColumn("Hello");
// When
var result = Record.Exception(() => table.AddRow("Foo", "Bar"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("The number of row columns are greater than the number of table columns.");
}
}
[UsesVerify]
public sealed class TheAddEmptyRowMethod
{
[Fact]
[Expectation("AddEmptyRow")]
public Task Should_Render_Table_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table();
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddEmptyRow();
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
}
[Fact]
[Expectation("Render")]
public Task Should_Render_Table_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table();
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Footers")]
public Task Should_Render_Table_With_Footers_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table();
table.AddColumn(new TableColumn("Foo").Footer("Oof").RightAligned());
table.AddColumn("Bar");
table.AddColumns(new TableColumn("Baz").Footer("Zab"));
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_LeftAligned")]
public Task Should_Left_Align_Table_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table();
table.Alignment = Justify.Left;
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Centered")]
public Task Should_Center_Table_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table();
table.Alignment = Justify.Center;
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_RightAligned")]
public Task Should_Right_Align_Table_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table();
table.Alignment = Justify.Right;
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Nested")]
public Task Should_Render_Table_Nested_In_Panels_Correctly()
{
// A simple table
var console = new TestConsole();
var table = new Table() { Border = TableBorder.Rounded };
table.AddColumn("Foo");
table.AddColumn("Bar");
table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Right });
table.AddRow("Qux\nQuuuuuux", "[blue]Corgi[/]", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// Render a table in some panels.
console.Write(new Panel(new Panel(table)
{
Border = BoxBorder.Ascii,
}));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_ColumnJustification")]
public Task Should_Render_Table_With_Column_Justification_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table();
table.AddColumn(new TableColumn("Foo") { Alignment = Justify.Left });
table.AddColumn(new TableColumn("Bar") { Alignment = Justify.Right });
table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Center });
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Lorem ipsum dolor sit amet");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Expand")]
public Task Should_Expand_Table_To_Available_Space_If_Specified()
{
// Given
var console = new TestConsole();
var table = new Table() { Expand = true };
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Multiline")]
public Task Should_Render_Table_With_Multiple_Rows_In_Cell_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table();
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux\nQuuux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_CellPadding")]
public Task Should_Render_Table_With_Cell_Padding_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table();
table.AddColumns("Foo", "Bar");
table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 0, 2, 0) });
table.AddRow("Qux\nQuuux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_NoRows")]
public Task Should_Render_Table_Without_Rows()
{
// Given
var console = new TestConsole();
var table = new Table();
table.AddColumns("Foo", "Bar");
table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 0, 2, 0) });
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Impossible")]
public Task Should_Not_Draw_Tables_That_Are_Impossible_To_Draw()
{
// Given
var console = new TestConsole().Width(25);
var first = new Table().Border(TableBorder.Rounded).BorderColor(Color.Red);
first.AddColumn(new TableColumn("[u]PS1[/]").Centered());
first.AddColumn(new TableColumn("[u]PS2[/]"));
first.AddColumn(new TableColumn("[u]PS3[/]"));
first.AddRow("Hello", "[red]World[/]", string.Empty);
first.AddRow("[blue]Bonjour[/]", "[white]le[/]", "[red]monde![/]");
first.AddRow("[blue]Hej[/]", "[yellow]Världen[/]", string.Empty);
var second = new Table().Border(TableBorder.Square).BorderColor(Color.Green);
second.AddColumn(new TableColumn("[u]Foo[/]"));
second.AddColumn(new TableColumn("[u]Bar[/]"));
second.AddColumn(new TableColumn("[u]Baz[/]"));
second.AddRow("Hello", "[red]World[/]", string.Empty);
second.AddRow(first, new Text("Whaaat"), new Text("Lolz"));
second.AddRow("[blue]Hej[/]", "[yellow]Världen[/]", string.Empty);
var table = new Table().Border(TableBorder.Rounded);
table.AddColumn(new TableColumn(new Panel("[u]ABC[/]").BorderColor(Color.Red)));
table.AddColumn(new TableColumn(new Panel("[u]DEF[/]").BorderColor(Color.Green)));
table.AddColumn(new TableColumn(new Panel("[u]GHI[/]").BorderColor(Color.Blue)));
table.AddRow(new Text("Hello").Centered(), new Markup("[red]World[/]"), Text.Empty);
table.AddRow(second, new Text("Whaat"), new Text("Lol").RightAligned());
table.AddRow(new Markup("[blue]Hej[/]"), new Markup("[yellow]Världen[/]"), Text.Empty);
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Title_Caption")]
public Task Should_Render_Table_With_Title_And_Caption_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table { Border = TableBorder.Rounded };
table.Title = new TableTitle("Hello World");
table.Caption = new TableTitle("Goodbye World");
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Title_Caption_LeftAligned")]
public Task Should_Left_Align_Table_With_Title_And_Caption_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table { Border = TableBorder.Rounded };
table.LeftAligned();
table.Title = new TableTitle("Hello World");
table.Caption = new TableTitle("Goodbye World");
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Title_Caption_Centered")]
public Task Should_Center_Table_With_Title_And_Caption_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table { Border = TableBorder.Rounded };
table.Centered();
table.Title = new TableTitle("Hello World");
table.Caption = new TableTitle("Goodbye World");
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Title_Caption_RightAligned")]
public Task Should_Right_Align_Table_With_Title_And_Caption_Correctly()
{
// Given
var console = new TestConsole();
var table = new Table { Border = TableBorder.Rounded };
table.RightAligned();
table.Title = new TableTitle("Hello World");
table.Caption = new TableTitle("Goodbye World");
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Write(table);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_Fold")]
public Task Should_Render_With_Folded_Text_Table_Correctly()
{
// Given
var console = new TestConsole().Width(30);
var table = new Table();
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux With A Long Description", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred On A Long Long Walk");
var panel = new Panel(table);
panel.Border = BoxBorder.Double;
// When
console.Write(panel);
// Then
return Verifier.Verify(console.Output);
}
}
}

View File

@@ -0,0 +1,222 @@
using System;
using System.Threading.Tasks;
using Spectre.Console.Testing;
using Shouldly;
using VerifyXunit;
using Xunit;
using Spectre.Verify.Extensions;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/Prompt/Text")]
public sealed class TextPromptTests
{
[Fact]
[Expectation("ConversionError")]
public Task Should_Return_Validation_Error_If_Value_Cannot_Be_Converted()
{
// Given
var console = new TestConsole();
console.Input.PushTextWithEnter("ninety-nine");
console.Input.PushTextWithEnter("99");
// When
console.Prompt(new TextPrompt<int>("Age?"));
// Then
return Verifier.Verify(console.Lines);
}
[Fact]
[Expectation("DefaultValue")]
public Task Should_Chose_Default_Value_If_Nothing_Is_Entered()
{
// Given
var console = new TestConsole();
console.Input.PushKey(ConsoleKey.Enter);
// When
console.Prompt(
new TextPrompt<string>("Favorite fruit?")
.AddChoice("Banana")
.AddChoice("Orange")
.DefaultValue("Banana"));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("InvalidChoice")]
public Task Should_Return_Error_If_An_Invalid_Choice_Is_Made()
{
// Given
var console = new TestConsole();
console.Input.PushTextWithEnter("Apple");
console.Input.PushTextWithEnter("Banana");
// When
console.Prompt(
new TextPrompt<string>("Favorite fruit?")
.AddChoice("Banana")
.AddChoice("Orange")
.DefaultValue("Banana"));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("AcceptChoice")]
public Task Should_Accept_Choice_In_List()
{
// Given
var console = new TestConsole();
console.Input.PushTextWithEnter("Orange");
// When
console.Prompt(
new TextPrompt<string>("Favorite fruit?")
.AddChoice("Banana")
.AddChoice("Orange")
.DefaultValue("Banana"));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("AutoComplete_Empty")]
public Task Should_Auto_Complete_To_First_Choice_If_Pressing_Tab_On_Empty_String()
{
// Given
var console = new TestConsole();
console.Input.PushKey(ConsoleKey.Tab);
console.Input.PushKey(ConsoleKey.Enter);
// When
console.Prompt(
new TextPrompt<string>("Favorite fruit?")
.AddChoice("Banana")
.AddChoice("Orange")
.DefaultValue("Banana"));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("AutoComplete_BestMatch")]
public Task Should_Auto_Complete_To_Best_Match()
{
// Given
var console = new TestConsole();
console.Input.PushText("Band");
console.Input.PushKey(ConsoleKey.Tab);
console.Input.PushKey(ConsoleKey.Enter);
// When
console.Prompt(
new TextPrompt<string>("Favorite fruit?")
.AddChoice("Banana")
.AddChoice("Bandana")
.AddChoice("Orange"));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("AutoComplete_NextChoice")]
public Task Should_Auto_Complete_To_Next_Choice_When_Pressing_Tab_On_A_Match()
{
// Given
var console = new TestConsole();
console.Input.PushText("Apple");
console.Input.PushKey(ConsoleKey.Tab);
console.Input.PushKey(ConsoleKey.Enter);
// When
console.Prompt(
new TextPrompt<string>("Favorite fruit?")
.AddChoice("Apple")
.AddChoice("Banana")
.AddChoice("Orange"));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("CustomValidation")]
public Task Should_Return_Error_If_Custom_Validation_Fails()
{
// Given
var console = new TestConsole();
console.Input.PushTextWithEnter("22");
console.Input.PushTextWithEnter("102");
console.Input.PushTextWithEnter("ABC");
console.Input.PushTextWithEnter("99");
// When
console.Prompt(
new TextPrompt<int>("Guess number:")
.ValidationErrorMessage("Invalid input")
.Validate(age =>
{
if (age < 99)
{
return ValidationResult.Error("Too low");
}
else if (age > 99)
{
return ValidationResult.Error("Too high");
}
return ValidationResult.Success();
}));
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("CustomConverter")]
public Task Should_Use_Custom_Converter()
{
// Given
var console = new TestConsole();
console.Input.PushTextWithEnter("Banana");
// When
var result = console.Prompt(
new TextPrompt<(int, string)>("Favorite fruit?")
.AddChoice((1, "Apple"))
.AddChoice((2, "Banana"))
.WithConverter(testData => testData.Item2));
// Then
result.Item1.ShouldBe(2);
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("SecretDefaultValue")]
public Task Should_Chose_Masked_Default_Value_If_Nothing_Is_Entered_And_Prompt_Is_Secret()
{
// Given
var console = new TestConsole();
console.Input.PushKey(ConsoleKey.Enter);
// When
console.Prompt(
new TextPrompt<string>("Favorite fruit?")
.Secret()
.DefaultValue("Banana"));
// Then
return Verifier.Verify(console.Output);
}
}
}

View File

@@ -0,0 +1,158 @@
using Shouldly;
using Spectre.Console.Rendering;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class TextTests
{
public sealed class TheLengthProperty
{
[Theory]
[InlineData("Hello", 5)]
[InlineData("Hello\nWorld", 11)]
public void Should_Return_The_Number_Of_Characters(string input, int expected)
{
// Given
var markup = new Text(input);
// When
var result = markup.Length;
// Then
result.ShouldBe(expected);
}
}
public sealed class TheLinesProperty
{
[Theory]
[InlineData("Hello", 1)]
[InlineData("Hello\nWorld", 2)]
public void Should_Return_The_Number_Of_Lines(string input, int expected)
{
// Given
var markup = new Text(input);
// When
var result = markup.Lines;
// Then
result.ShouldBe(expected);
}
}
[Fact]
public void Should_Consider_The_Longest_Word_As_Minimum_Width()
{
// Given
var caps = new TestCapabilities { Unicode = true };
var text = new Text("Foo Bar Baz\nQux\nLol mobile");
// When
var result = ((IRenderable)text).Measure(caps.CreateRenderContext(), 80);
// Then
result.Min.ShouldBe(6);
}
[Fact]
public void Should_Consider_The_Longest_Line_As_Maximum_Width()
{
// Given
var caps = new TestCapabilities { Unicode = true };
var text = new Text("Foo Bar Baz\nQux\nLol mobile");
// When
var result = ((IRenderable)text).Measure(caps.CreateRenderContext(), 80);
// Then
result.Max.ShouldBe(11);
}
[Fact]
public void Should_Render_Unstyled_Text_As_Expected()
{
// Given
var console = new TestConsole();
var text = new Text("Hello World");
// When
console.Write(text);
// Then
console.Output.ShouldBe("Hello World");
}
[Theory]
[InlineData("Hello\n\nWorld\n\n")]
[InlineData("Hello\r\n\r\nWorld\r\n\r\n")]
public void Should_Write_Line_Breaks(string input)
{
// Given
var console = new TestConsole();
var text = new Text(input);
// When
console.Write(text);
// Then
console.Output.ShouldBe("Hello\n\nWorld\n\n");
}
[Fact]
public void Should_Render_Panel_2()
{
// Given
var console = new TestConsole();
// When
console.Write(new Markup("[b]Hello World[/]\n[yellow]Hello World[/]"));
// Then
console.Lines.Count.ShouldBe(2);
console.Lines[0].ShouldBe("Hello World");
console.Lines[1].ShouldBe("Hello World");
}
[Theory]
[InlineData(5, "Hello World", "Hello\nWorld")]
[InlineData(10, "Hello Sweet Nice World", "Hello \nSweet Nice\nWorld")]
public void Should_Split_Unstyled_Text_To_New_Lines_If_Width_Exceeds_Console_Width(
int width, string input, string expected)
{
// Given
var console = new TestConsole().Width(width);
var text = new Text(input);
// When
console.Write(text);
// Then
console.Output
.NormalizeLineEndings()
.ShouldBe(expected);
}
[Theory]
[InlineData(Overflow.Fold, "foo \npneumonoultram\nicroscopicsili\ncovolcanoconio\nsis bar qux")]
[InlineData(Overflow.Crop, "foo \npneumonoultram\nbar qux")]
[InlineData(Overflow.Ellipsis, "foo \npneumonoultra…\nbar qux")]
public void Should_Overflow_Text_Correctly(Overflow overflow, string expected)
{
// Given
var console = new TestConsole().Width(14);
var text = new Text("foo pneumonoultramicroscopicsilicovolcanoconiosis bar qux")
.Overflow(overflow);
// When
console.Write(text);
// Then
console.Output
.NormalizeLineEndings()
.ShouldBe(expected);
}
}
}

View File

@@ -0,0 +1,85 @@
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
[UsesVerify]
[ExpectationPath("Widgets/Tree")]
public class TreeTests
{
[Fact]
[Expectation("Render")]
public Task Should_Render_Tree_Correctly()
{
// Given
var console = new TestConsole();
var tree = new Tree(new Text("Root node")).Guide(TreeGuide.DoubleLine);
var nestedChildren = Enumerable.Range(0, 10).Select(x => new Text($"multiple\nline {x}"));
var child2 = new TreeNode(new Text("child2"));
var child2Child = new TreeNode(new Text("child2-1"));
child2.AddNode(child2Child);
child2Child.AddNode(new TreeNode(new Text("Child2-1-1\nchild")));
var child3 = new TreeNode(new Text("child3"));
var child3Child = new TreeNode(new Text("single leaf\nmultiline"));
child3Child.AddNode(new TreeNode(new Calendar(2021, 01)));
child3.AddNode(child3Child);
tree.AddNode("child1").AddNodes(nestedChildren);
tree.AddNode(child2);
tree.AddNode(child3);
tree.AddNode("child4");
// When
console.Write(tree);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
[Expectation("Render_NoChildren")]
public Task Should_Render_Tree_With_No_Child_Nodes_Correctly()
{
// Given
var console = new TestConsole();
var tree = new Tree(new Text("Root node"));
// When
console.Write(tree);
// Then
return Verifier.Verify(console.Output);
}
[Fact]
public void Should_Throw_If_Tree_Contains_Cycles()
{
// Given
var console = new TestConsole();
var child2 = new TreeNode(new Text("child 2"));
var child3 = new TreeNode(new Text("child 3"));
var child1 = new TreeNode(new Text("child 1"));
child1.AddNodes(child2, child3);
var root = new TreeNode(new Text("Branch Node"));
root.AddNodes(child1);
child2.AddNode(root);
var tree = new Tree("root node");
tree.AddNodes(root);
// When
var result = Record.Exception(() => console.Write(tree));
// Then
result.ShouldBeOfType<CircularTreeException>();
}
}
}