|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Aspire.Dashboard.ConsoleLogs;
using Xunit;
namespace Aspire.Dashboard.Tests.ConsoleLogsTests;
public class AnsiParserTests
{
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData("This is some text without any codes")]
[InlineData("This is some text \x1C with an invalid code")]
public void ConvertToHtml_ReturnsInputUnchangedIfNoCodesPresent(string input)
{
var expectedOutput = input;
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
}
[Theory]
[InlineData("\x1B[2m", "")]
[InlineData("\x1B[2A", "")]
[InlineData("\x1B[u", "")]
[InlineData("\x1B[2@", "")]
[InlineData("\x1B[2~", "")]
[InlineData("\x1B[?25h", "")]
[InlineData("\x1B[?25l", "")]
[InlineData("\x1B[23m", "")]
[InlineData("\x1B[2m\x1B[23m", "")]
[InlineData("Real Text Before\x1B[2m\x1B[23m", "Real Text Before")]
[InlineData("\x1B[2m\x1B[23mReal Text After", "Real Text After")]
[InlineData("\x1B[2mReal Text Between\x1B[23m", "Real Text Between")]
[InlineData("Real Text Before\x1B[2mReal Text Between\x1B[23mReal Text After", "Real Text BeforeReal Text BetweenReal Text After")]
public void ConvertToHtml_IgnoresUnsupportedButValidCodes(string input, string expectedOutput)
{
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(default, result.ResidualState);
}
[Theory]
[InlineData("\x1b]9;4;3;\x1b\\", "")]
[InlineData("\x1b]9;4;3;\x07", "")]
[InlineData("Real Text Before\x1b]9;4;3;\x1b\\", "Real Text Before")]
[InlineData("\x1b]9;4;3;\x1b\\Real Text After", "Real Text After")]
[InlineData("\u001b]9;4;3;\u001b\\Real Text Between\u001b]9;4;3;\u001b\\", "Real Text Between")]
public void ConvertToHtml_IgnoresUnsupportedConEmuCodes(string input, string expectedOutput)
{
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(default, result.ResidualState);
}
[Fact]
public void ConvertToHtml_ColorOpenedButNotClosed_AutoClosedWithResidualState()
{
var input = "\x1B[32mThis is some green text";
var expectedOutput = "<span class=\"ansi-fg-green\">This is some green text</span>";
var expectedResidualState = new AnsiParser.ParserState() { ForegroundColor = ConsoleColor.Green };
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(expectedResidualState, result.ResidualState);
}
[Fact]
public void ConvertToHtml_ColorOpenedAndClosed()
{
var input = "\x1B[32mThis is some green text\x1B[39m";
var expectedOutput = "<span class=\"ansi-fg-green\">This is some green text</span>";
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(default, result.ResidualState);
}
[Fact]
public void ConvertToHtml_ColorOpenedAndClosedWithIgnoredSequencesInMiddle()
{
var input = "\x1B[32mThis is \x1B[?25hsome green\u001b]9;4;3;\u001b\\ text\x1B[39m";
var expectedOutput = "<span class=\"ansi-fg-green\">This is some green text</span>";
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(default, result.ResidualState);
}
[Fact]
public void ConvertToHtml_WithForegroundAndBackgroundSet()
{
var input = "\x1B[32m\x1B[42mThis is some green text with a green background\x1B[39m\x1B[49m";
var expectedOutput = "<span class=\"ansi-fg-green ansi-bg-green\">This is some green text with a green background</span>";
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(default, result.ResidualState);
}
[Fact]
public void ConvertToHtml_PartiallyFormatted()
{
var input = "\x1B[32mThis is some green text\x1B[39m and this is some plain text";
var expectedOutput = "<span class=\"ansi-fg-green\">This is some green text</span> and this is some plain text";
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(default, result.ResidualState);
}
[Fact]
public void ConvertToHtml_MultipleDistinctFormats()
{
var input = "\x1B[32mGreen text\x1B[39m Plain text\x1B[42m Green background\x1B[49m";
var expectedOutput = "<span class=\"ansi-fg-green\">Green text</span> Plain text<span class=\"ansi-bg-green\"> Green background</span>";
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(default, result.ResidualState);
}
[Fact]
public void ConvertToHtml_OverlappingFormats()
{
var input = "\x1B[32mGreen text\x1B[42m Green text Green background\x1B[39m Green background\x1B[49m";
var expectedOutput = "<span class=\"ansi-fg-green\">Green text</span><span class=\"ansi-fg-green ansi-bg-green\"> Green text Green background</span><span class=\"ansi-bg-green\"> Green background</span>";
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(default, result.ResidualState);
}
[Fact]
public void ConvertToHtml_FormattingAcrossLines()
{
var input1 = "\x1B[32mThis is some green text";
var input2 = "This is some more green text\u001b[39m";
var expectedOutput1 = "<span class=\"ansi-fg-green\">This is some green text</span>";
var expectedOutput2 = "<span class=\"ansi-fg-green\">This is some more green text</span>";
var result1 = AnsiParser.ConvertToHtml(input1);
var result2 = AnsiParser.ConvertToHtml(input2, result1.ResidualState);
Assert.Equal(expectedOutput1, result1.ConvertedText);
Assert.Equal(new AnsiParser.ParserState() { ForegroundColor = ConsoleColor.Green }, result1.ResidualState);
Assert.Equal(expectedOutput2, result2.ConvertedText);
Assert.Equal(default, result2.ResidualState);
}
[Fact]
public void ConvertToHtml_OverlappingFormattingAcrossLines()
{
var input1 = "\x1B[32mGreen text\x1B[42m Green text";
var input2 = "Green background\x1B[39m Green background\x1B[49m";
var expectedOutput1 = "<span class=\"ansi-fg-green\">Green text</span><span class=\"ansi-fg-green ansi-bg-green\"> Green text</span>";
var expectedOutput2 = "<span class=\"ansi-fg-green ansi-bg-green\">Green background</span><span class=\"ansi-bg-green\"> Green background</span>";
var result1 = AnsiParser.ConvertToHtml(input1);
var result2 = AnsiParser.ConvertToHtml(input2, result1.ResidualState);
Assert.Equal(expectedOutput1, result1.ConvertedText);
Assert.Equal(new AnsiParser.ParserState() { ForegroundColor = ConsoleColor.Green, BackgroundColor = ConsoleColor.Green }, result1.ResidualState);
Assert.Equal(expectedOutput2, result2.ConvertedText);
Assert.Equal(default, result2.ResidualState);
}
[Theory]
[InlineData("\x1B[30mBlack\x1B[39m", "<span class=\"ansi-fg-black\">Black</span>")]
[InlineData("\x1B[31mRed\x1B[39m", "<span class=\"ansi-fg-red\">Red</span>")]
[InlineData("\x1B[32mGreen\x1B[39m", "<span class=\"ansi-fg-green\">Green</span>")]
[InlineData("\x1B[33mYellow\x1B[39m", "<span class=\"ansi-fg-yellow\">Yellow</span>")]
[InlineData("\x1B[34mBlue\x1B[39m", "<span class=\"ansi-fg-blue\">Blue</span>")]
[InlineData("\x1B[35mMagenta\x1B[39m", "<span class=\"ansi-fg-magenta\">Magenta</span>")]
[InlineData("\x1B[36mCyan\x1B[39m", "<span class=\"ansi-fg-cyan\">Cyan</span>")]
[InlineData("\x1B[37mWhite\x1B[39m", "<span class=\"ansi-fg-white\">White</span>")]
[InlineData("\x1B[1m\x1B[30mBright Black\x1B[22m\x1B[39m", "<span class=\"ansi-fg-brightblack\">Bright Black</span>")]
[InlineData("\x1B[1m\x1B[31mBright Red\x1b[22m\x1B[39m", "<span class=\"ansi-fg-brightred\">Bright Red</span>")]
[InlineData("\x1B[1m\x1B[32mBright Green\x1B[22m\x1B[39m", "<span class=\"ansi-fg-brightgreen\">Bright Green</span>")]
[InlineData("\x1B[1m\x1B[33mBright Yellow\x1B[22m\x1B[39m", "<span class=\"ansi-fg-brightyellow\">Bright Yellow</span>")]
[InlineData("\x1B[1m\x1B[34mBright Blue\x1B[22m\x1B[39m", "<span class=\"ansi-fg-brightblue\">Bright Blue</span>")]
[InlineData("\x1B[1m\x1B[35mBright Magenta\x1B[22m\x1B[39m", "<span class=\"ansi-fg-brightmagenta\">Bright Magenta</span>")]
[InlineData("\x1B[1m\x1B[36mBright Cyan\x1B[22m\x1B[39m", "<span class=\"ansi-fg-brightcyan\">Bright Cyan</span>")]
[InlineData("\x1B[1m\x1B[37mBright White\x1B[22m\x1B[39m", "<span class=\"ansi-fg-brightwhite\">Bright White</span>")]
public void ConvertToHtml_ForegroundColorSupport(string input, string expectedOutput)
{
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(default, result.ResidualState);
}
[Theory]
[InlineData("\x1B[40mBlack\x1B[49m", "<span class=\"ansi-bg-black\">Black</span>")]
[InlineData("\x1B[41mRed\x1B[49m", "<span class=\"ansi-bg-red\">Red</span>")]
[InlineData("\x1B[42mGreen\x1B[49m", "<span class=\"ansi-bg-green\">Green</span>")]
[InlineData("\x1B[43mYellow\x1B[49m", "<span class=\"ansi-bg-yellow\">Yellow</span>")]
[InlineData("\x1B[44mBlue\x1B[49m", "<span class=\"ansi-bg-blue\">Blue</span>")]
[InlineData("\x1B[45mMagenta\x1B[49m", "<span class=\"ansi-bg-magenta\">Magenta</span>")]
[InlineData("\x1B[46mCyan\x1B[49m", "<span class=\"ansi-bg-cyan\">Cyan</span>")]
[InlineData("\x1B[47mWhite\x1B[49m", "<span class=\"ansi-bg-white\">White</span>")]
public void ConvertToHtml_BackgroundColorSupport(string input, string expectedOutput)
{
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(default, result.ResidualState);
}
[Fact]
public void ConvertToHtml_HandlesResetIntensity()
{
var input = "\x1B[1m\x1B[32m\x1B[42mBright Green Text on Green Background\x1B[22mNo Longer Bright";
var expectedOutput = "<span class=\"ansi-fg-brightgreen ansi-bg-green\">Bright Green Text on Green Background</span><span class=\"ansi-fg-green ansi-bg-green\">No Longer Bright</span>";
var expectedResidualState = new AnsiParser.ParserState() { ForegroundColor = ConsoleColor.Green, BackgroundColor = ConsoleColor.Green };
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(expectedResidualState, result.ResidualState);
}
[Fact]
public void ConvertToHtml_HandlesDefaultForeground()
{
var input = "\x1B[1m\x1B[32m\x1B[42mBright Green Text on Green Background\x1B[39mNo Longer Green Text";
var expectedOutput = "<span class=\"ansi-fg-brightgreen ansi-bg-green\">Bright Green Text on Green Background</span><span class=\"ansi-bg-green\">No Longer Green Text</span>";
var expectedResidualState = new AnsiParser.ParserState() { Bright = true, BackgroundColor = ConsoleColor.Green };
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(expectedResidualState, result.ResidualState);
}
[Fact]
public void ConvertToHtml_HandlesDefaultBackground()
{
var input = "\x1B[1m\x1B[32m\x1B[42mBright Green Text on Green Background\x1B[49mNo Longer Green Background";
var expectedOutput = "<span class=\"ansi-fg-brightgreen ansi-bg-green\">Bright Green Text on Green Background</span><span class=\"ansi-fg-brightgreen\">No Longer Green Background</span>";
var expectedResidualState = new AnsiParser.ParserState() { Bright = true, ForegroundColor = ConsoleColor.Green };
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
Assert.Equal(expectedResidualState, result.ResidualState);
}
[Fact]
public void ConvertToHtml_HandlesMultipleParameterInSingleEscape()
{
var input = "\x1B[31;42;3;4mThis text is red, italic and underlined.";
var expectedOutput = "<span class=\"ansi-fg-red ansi-bg-green ansi-underline ansi-italic\">This text is red, italic and underlined.</span>";
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
}
[Theory]
[InlineData("\x1B[38;5;100mtext", "<span style=\"color: #878700\">text</span>")]
[InlineData("\x1B[38;5;200mtext", "<span style=\"color: #ff00d7\">text</span>")]
[InlineData("\x1B[38;5;245mtext", "<span style=\"color: #8a8a8a\">text</span>")]
[InlineData("\x1B[38;5;231mtext", "<span style=\"color: #ffffff\">text</span>")]
[InlineData("\x1B[38;5;255mtext", "<span style=\"color: #eeeeee\">text</span>")]
[InlineData("\x1B[38;5;0mtext", "<span style=\"color: #000000\">text</span>")]
public void ConvertToHtml_HandlesForegroundXtermColorCode(string input, string expectedOutput)
{
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
}
[Theory]
[InlineData("\x1B[48;5;100mtext", "<span style=\"background-color: #878700\">text</span>")]
[InlineData("\x1B[48;5;200mtext", "<span style=\"background-color: #ff00d7\">text</span>")]
[InlineData("\x1B[48;5;245mtext", "<span style=\"background-color: #8a8a8a\">text</span>")]
[InlineData("\x1B[48;5;231mtext", "<span style=\"background-color: #ffffff\">text</span>")]
[InlineData("\x1B[48;5;255mtext", "<span style=\"background-color: #eeeeee\">text</span>")]
[InlineData("\x1B[48;5;0mtext", "<span style=\"background-color: #000000\">text</span>")]
public void ConvertToHtml_HandlesBackgroundXtermColorCode(string input, string expectedOutput)
{
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
}
[Theory]
[InlineData("\x1B[4;38;5;100mtext", "<span class=\"ansi-underline\" style=\"color: #878700\">text</span>")]
[InlineData("\x1B[4;9;38;5;100mtext", "<span class=\"ansi-underline ansi-strikethrough\" style=\"color: #878700\">text</span>")]
public void ConvertToHtml_HandlesCombinedXtermColorAndModes(string input, string expectedOutput)
{
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
}
[Theory]
[InlineData("\x1B[3mtext", "<span class=\"ansi-italic\">text</span>")]
[InlineData("\x1B[4mtext", "<span class=\"ansi-underline\">text</span>")]
[InlineData("\x1B[9mtext", "<span class=\"ansi-strikethrough\">text</span>")]
public void ConvertToHtml_HandlesModes(string input, string expectedOutput)
{
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
}
[Fact]
public void ConvertToHtml_HandlesInvalidAnsi256ColorCode()
{
var input = "\x1B[38;5;300mInvalid color\x1B[0m";
var expectedOutput = "Invalid color";
var result = AnsiParser.ConvertToHtml(input);
Assert.Equal(expectedOutput, result.ConvertedText);
}
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData("This is some text without any codes")]
[InlineData("This is some text \x1B with an invalid code")]
[InlineData("\x1B This is some text that starts with an invalid code")]
[InlineData("This is some text that ends with an invalid code \x1B")]
[InlineData("\x1B Multiple \x1B invalid \x1B\x1B codes \x1B")]
[InlineData("!\x1B!\x1B!\x1B\x1B!!\x1B!")]
public void StripControlSequences_ReturnsInputUnchangedIfNoCodesPresent(string input)
{
var expectedOutput = input;
var result = AnsiParser.StripControlSequences(input);
Assert.Equal(expectedOutput, result);
}
[Theory]
[InlineData("\x1B[31mError:\x1B[0m \x1B[1m\x1B[4mFile not found\x1B[0m in directory \x1B[34m/home/user/docs\x1B[0m", "Error: File not found in directory /home/user/docs")]
[InlineData("\x1B[32mSuccess:\x1B[0m \x1B[1m\x1B[3mOperation completed successfully\x1B[0m", "Success: Operation completed successfully")]
[InlineData("\x1B[33mWarning:\x1B[0m \x1B[4mLow disk space\x1B[0m on drive \x1B[35mC:\x1B[0m", "Warning: Low disk space on drive C:")]
[InlineData("\x1B[36mInfo:\x1B[0m \x1B[1m\x1B[3mBackup completed\x1B[0m at \x1B[32m12:00 PM\x1B[0m", "Info: Backup completed at 12:00 PM")]
[InlineData("\x1B[36m", "")]
[InlineData("Ends \x1B[36m", "Ends ")]
[InlineData("\x1B[36m Starts", " Starts")]
[InlineData("\x1B[36m\x1B[36m", "")]
[InlineData("1\x1B[36m2\x1B[36m3", "123")]
public void StripControlSequences_StripsCorrectly(string input, string expectedOutput)
{
var result = AnsiParser.StripControlSequences(input);
Assert.Equal(expectedOutput, result);
}
}
|