diff --git a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs index d1d319b54..6535c978d 100644 --- a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs +++ b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs @@ -16,9 +16,9 @@ public class AnsiResponseParserTests (ITestOutputHelper output) [Fact] public void TestInputProcessing () { - string ansiStream = "\x1B[<0;10;20M" + // ANSI escape for mouse move at (10, 20) + string ansiStream = "\u001b[<0;10;20M" + // ANSI escape for mouse move at (10, 20) "Hello" + // User types "Hello" - "\x1B[0c"; // Device Attributes response (e.g., terminal identification i.e. DAR) + "\u001b[0c"; // Device Attributes response (e.g., terminal identification i.e. DAR) string? response1 = null; @@ -39,7 +39,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output) } // We see the M terminator - AssertReleased (ansiStream, ref i, "\x1B[<0;10;20M"); + AssertReleased (ansiStream, ref i, "\u001b[<0;10;20M"); // Regular user typing for (int c = 0; c < "Hello".Length; c++) @@ -48,7 +48,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output) } // Now we have entered the actual DAR we should be consuming these - for (int c = 0; c < "\x1B[0".Length; c++) + for (int c = 0; c < "\u001b[0".Length; c++) { AssertConsumed (ansiStream, ref i); } @@ -58,48 +58,48 @@ public class AnsiResponseParserTests (ITestOutputHelper output) Assert.Null (response1); AssertConsumed (ansiStream, ref i); Assert.NotNull (response2); - Assert.Equal ("\x1B[0c", response2); + Assert.Equal ("\u001b[0c", response2); Assert.NotNull (response2); - Assert.Equal ("\x1B[0c", response2); + Assert.Equal ("\u001b[0c", response2); } [Theory] - [InlineData ("\x1B[<0;10;20MHi\x1B[0c", "c", "\x1B[0c", "\x1B[<0;10;20MHi")] - [InlineData ("\x1B[<1;15;25MYou\x1B[1c", "c", "\x1B[1c", "\x1B[<1;15;25MYou")] - [InlineData ("\x1B[0cHi\x1B[0c", "c", "\x1B[0c", "Hi\x1B[0c")] - [InlineData ("\x1B[<0;0;0MHe\x1B[3c", "c", "\x1B[3c", "\x1B[<0;0;0MHe")] - [InlineData ("\x1B[<0;1;2Da\x1B[0c\x1B[1c", "c", "\x1B[0c", "\x1B[<0;1;2Da\x1B[1c")] - [InlineData ("\x1B[1;1M\x1B[3cAn", "c", "\x1B[3c", "\x1B[1;1MAn")] - [InlineData ("hi\x1B[2c\x1B[<5;5;5m", "c", "\x1B[2c", "hi\x1B[<5;5;5m")] - [InlineData ("\x1B[3c\x1B[4c\x1B[<0;0;0MIn", "c", "\u001b[3c", "\u001b[4c\u001b[<0;0;0MIn")] - [InlineData ("\x1B[<1;2;3M\x1B[0c\x1B[<1;2;3M\x1B[2c", "c", "\x1B[0c", "\x1B[<1;2;3M\x1B[<1;2;3M\u001b[2c")] - [InlineData ("\x1B[<0;1;1MHi\x1B[6c\x1B[2c\x1B[<1;0;0MT", "c", "\x1B[6c", "\x1B[<0;1;1MHi\x1B[2c\x1B[<1;0;0MT")] - [InlineData ("Te\x1B[<2;2;2M\x1B[7c", "c", "\x1B[7c", "Te\x1B[<2;2;2M")] - [InlineData ("\x1B[0c\x1B[<0;0;0M\x1B[3c\x1B[0c\x1B[1;0MT", "c", "\x1B[0c", "\x1B[<0;0;0M\x1B[3c\x1B[0c\x1B[1;0MT")] - [InlineData ("\x1B[0;0M\x1B[<0;0;0M\x1B[3cT\x1B[1c", "c", "\u001b[3c", "\u001b[0;0M\u001b[<0;0;0MT\u001b[1c")] - [InlineData ("\x1B[3c\x1B[<0;0;0M\x1B[0c\x1B[<1;1;1MIn\x1B[1c", "c", "\u001b[3c", "\u001b[<0;0;0M\u001b[0c\u001b[<1;1;1MIn\u001b[1c")] - [InlineData ("\x1B[<5;5;5M\x1B[7cEx\x1B[8c", "c", "\x1B[7c", "\u001b[<5;5;5MEx\u001b[8c")] + [InlineData ("\u001b[<0;10;20MHi\u001b[0c", "c", "\u001b[0c", "\u001b[<0;10;20MHi")] + [InlineData ("\u001b[<1;15;25MYou\u001b[1c", "c", "\u001b[1c", "\u001b[<1;15;25MYou")] + [InlineData ("\u001b[0cHi\u001b[0c", "c", "\u001b[0c", "Hi\u001b[0c")] + [InlineData ("\u001b[<0;0;0MHe\u001b[3c", "c", "\u001b[3c", "\u001b[<0;0;0MHe")] + [InlineData ("\u001b[<0;1;2Da\u001b[0c\u001b[1c", "c", "\u001b[0c", "\u001b[<0;1;2Da\u001b[1c")] + [InlineData ("\u001b[1;1M\u001b[3cAn", "c", "\u001b[3c", "\u001b[1;1MAn")] + [InlineData ("hi\u001b[2c\u001b[<5;5;5m", "c", "\u001b[2c", "hi\u001b[<5;5;5m")] + [InlineData ("\u001b[3c\u001b[4c\u001b[<0;0;0MIn", "c", "\u001b[3c", "\u001b[4c\u001b[<0;0;0MIn")] + [InlineData ("\u001b[<1;2;3M\u001b[0c\u001b[<1;2;3M\u001b[2c", "c", "\u001b[0c", "\u001b[<1;2;3M\u001b[<1;2;3M\u001b[2c")] + [InlineData ("\u001b[<0;1;1MHi\u001b[6c\u001b[2c\u001b[<1;0;0MT", "c", "\u001b[6c", "\u001b[<0;1;1MHi\u001b[2c\u001b[<1;0;0MT")] + [InlineData ("Te\u001b[<2;2;2M\u001b[7c", "c", "\u001b[7c", "Te\u001b[<2;2;2M")] + [InlineData ("\u001b[0c\u001b[<0;0;0M\u001b[3c\u001b[0c\u001b[1;0MT", "c", "\u001b[0c", "\u001b[<0;0;0M\u001b[3c\u001b[0c\u001b[1;0MT")] + [InlineData ("\u001b[0;0M\u001b[<0;0;0M\u001b[3cT\u001b[1c", "c", "\u001b[3c", "\u001b[0;0M\u001b[<0;0;0MT\u001b[1c")] + [InlineData ("\u001b[3c\u001b[<0;0;0M\u001b[0c\u001b[<1;1;1MIn\u001b[1c", "c", "\u001b[3c", "\u001b[<0;0;0M\u001b[0c\u001b[<1;1;1MIn\u001b[1c")] + [InlineData ("\u001b[<5;5;5M\u001b[7cEx\u001b[8c", "c", "\u001b[7c", "\u001b[<5;5;5MEx\u001b[8c")] // Random characters and mixed inputs - [InlineData ("\x1B[<1;1;1MJJ\x1B[9c", "c", "\x1B[9c", "\x1B[<1;1;1MJJ")] // Mixed text - [InlineData ("Be\x1B[0cAf", "c", "\x1B[0c", "BeAf")] // Escape in the middle of the string - [InlineData ("\x1B[<0;0;0M\x1B[2cNot e", "c", "\x1B[2c", "\x1B[<0;0;0MNot e")] // Unexpected sequence followed by text - [InlineData ("Just te\x1B[<0;0;0M\x1B[3c\x1B[2c\x1B[4c", "c", "\x1B[3c", "Just te\x1B[<0;0;0M\x1B[2c\x1B[4c")] // Multiple unexpected responses - [InlineData ("\x1B[1;2;3M\x1B[0c\x1B[2;2M\x1B[0;0;0MTe", "c", "\x1B[0c", "\x1B[1;2;3M\x1B[2;2M\x1B[0;0;0MTe")] // Multiple commands with responses - [InlineData ("\x1B[<3;3;3Mabc\x1B[4cde", "c", "\x1B[4c", "\x1B[<3;3;3Mabcde")] // Escape sequences mixed with regular text + [InlineData ("\u001b[<1;1;1MJJ\u001b[9c", "c", "\u001b[9c", "\u001b[<1;1;1MJJ")] // Mixed text + [InlineData ("Be\u001b[0cAf", "c", "\u001b[0c", "BeAf")] // Escape in the middle of the string + [InlineData ("\u001b[<0;0;0M\u001b[2cNot e", "c", "\u001b[2c", "\u001b[<0;0;0MNot e")] // Unexpected sequence followed by text + [InlineData ("Just te\u001b[<0;0;0M\u001b[3c\u001b[2c\u001b[4c", "c", "\u001b[3c", "Just te\u001b[<0;0;0M\u001b[2c\u001b[4c")] // Multiple unexpected responses + [InlineData ("\u001b[1;2;3M\u001b[0c\u001b[2;2M\u001b[0;0;0MTe", "c", "\u001b[0c", "\u001b[1;2;3M\u001b[2;2M\u001b[0;0;0MTe")] // Multiple commands with responses + [InlineData ("\u001b[<3;3;3Mabc\u001b[4cde", "c", "\u001b[4c", "\u001b[<3;3;3Mabcde")] // Escape sequences mixed with regular text // Edge cases - [InlineData ("\x1B[0c\x1B[0c\x1B[0c", "c", "\x1B[0c", "\x1B[0c\x1B[0c")] // Multiple identical responses + [InlineData ("\u001b[0c\u001b[0c\u001b[0c", "c", "\u001b[0c", "\u001b[0c\u001b[0c")] // Multiple identical responses [InlineData ("", "c", "", "")] // Empty input [InlineData ("Normal", "c", "", "Normal")] // No escape sequences - [InlineData ("\x1B[<0;0;0M", "c", "", "\x1B[<0;0;0M")] // Escape sequence only - [InlineData ("\x1B[1;2;3M\x1B[0c", "c", "\x1B[0c", "\x1B[1;2;3M")] // Last response consumed + [InlineData ("\u001b[<0;0;0M", "c", "", "\u001b[<0;0;0M")] // Escape sequence only + [InlineData ("\u001b[1;2;3M\u001b[0c", "c", "\u001b[0c", "\u001b[1;2;3M")] // Last response consumed - [InlineData ("Inpu\x1B[0c\x1B[1;0;0M", "c", "\x1B[0c", "Inpu\x1B[1;0;0M")] // Single input followed by escape - [InlineData ("\x1B[2c\x1B[<5;6;7MDa", "c", "\x1B[2c", "\x1B[<5;6;7MDa")] // Multiple escape sequences followed by text - [InlineData ("\x1B[0cHi\x1B[1cGo", "c", "\x1B[0c", "Hi\u001b[1cGo")] // Normal text with multiple escape sequences + [InlineData ("Inpu\u001b[0c\u001b[1;0;0M", "c", "\u001b[0c", "Inpu\u001b[1;0;0M")] // Single input followed by escape + [InlineData ("\u001b[2c\u001b[<5;6;7MDa", "c", "\u001b[2c", "\u001b[<5;6;7MDa")] // Multiple escape sequences followed by text + [InlineData ("\u001b[0cHi\u001b[1cGo", "c", "\u001b[0c", "Hi\u001b[1cGo")] // Normal text with multiple escape sequences - [InlineData ("\x1B[<1;1;1MTe", "c", "", "\x1B[<1;1;1MTe")] + [InlineData ("\u001b[<1;1;1MTe", "c", "", "\u001b[<1;1;1MTe")] // Add more test cases here... public void TestInputSequences (string ansiStream, string expectedTerminator, string expectedResponse, string expectedOutput) { @@ -149,7 +149,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output) [Fact] public void ReleasesEscapeAfterTimeout () { - string input = "\x1B"; + string input = "\u001b"; int i = 0; // Esc on its own looks like it might be an esc sequence so should be consumed @@ -170,19 +170,48 @@ public class AnsiResponseParserTests (ITestOutputHelper output) public void TwoExcapesInARow () { // Example user presses Esc key then a DAR comes in - string input = "\x1B\x1B"; + string input = "\u001b\u001b"; int i = 0; // First Esc gets grabbed AssertConsumed (input, ref i); // Upon getting the second Esc we should release the first - AssertReleased (input, ref i, "\x1B",0); + AssertReleased (input, ref i, "\u001b",0); // Assume 50ms or something has passed, lets force release as no new content // It should be the second escape that gets released (i.e. index 1) - AssertManualReleaseIs ("\x1B",1); + AssertManualReleaseIs ("\u001b",1); + } + + [Fact] + public void TwoExcapesInARowWithTextBetween () + { + // Example user presses Esc key and types at the speed of light (normally the consumer should be handling Esc timeout) + // then a DAR comes in. + string input = "\u001bfish\u001b"; + int i = 0; + + // First Esc gets grabbed + AssertConsumed (input, ref i); // Esc + Assert.Equal (ParserState.ExpectingBracket,_parser1.State); + Assert.Equal (ParserState.ExpectingBracket, _parser2.State); + + // Because next char is 'f' we do not see a bracket so release both + AssertReleased (input, ref i, "\u001bf", 0,1); // f + + Assert.Equal (ParserState.Normal, _parser1.State); + Assert.Equal (ParserState.Normal, _parser2.State); + + AssertReleased (input, ref i,"i",2); + AssertReleased (input, ref i, "s", 3); + AssertReleased (input, ref i, "h", 4); + + AssertConsumed (input, ref i); // Second Esc + + // Assume 50ms or something has passed, lets force release as no new content + AssertManualReleaseIs ("\u001b", 5); } private Tuple [] StringToBatch (string batch)