Files
PowerShell-PSReadLine/test/RenderTest.cs
T
Andy Jordan 06e3b4bcb6 Add fixture to enable existing tests for screen reader mode
Tests that depend on supported features such as menu completions, list
view, inline predictions, continuation prompt, and colors are skipped.

The "helper" module for running tests from the command-line and in
AppVeyor is minimally updated to not skip the new fixture.

A `BlankRestOfBuffer()` function implements the semantics of the `0J`
control sequence in the mock console.
2025-08-27 17:46:47 -07:00

345 lines
14 KiB
C#

using System;
using System.Management.Automation;
using System.Text;
using Microsoft.PowerShell;
using Xunit;
namespace Test
{
public partial class ReadLine
{
[SkippableFact]
public void ClearScreen()
{
TestSetup(KeyMode.Emacs);
Test("echo 1\necho 2\necho 3", Keys(
"echo 1",
_.Shift_Enter,
"echo 2",
_.Shift_Enter,
"echo 3"));
AssertCursorTopIs(3);
Test("echo foo", Keys(
"echo foo"
), resetCursor: false);
AssertCursorTopIs(4);
Test("echo zed", Keys(
"echo zed",
_.Ctrl_l,
CheckThat(() => AssertCursorTopIs(0))
), resetCursor: false);
}
[SkippableFact]
public void Render()
{
TestSetup(KeyMode.Cmd);
Test("", Keys(
"abc -def <#123#> \"hello $name\" 1 + (1-2)",
_.Home,
CheckThat(() =>
AssertScreenIs(1,
TokenClassification.Command, "abc",
TokenClassification.None, " ",
TokenClassification.Parameter, "-def",
TokenClassification.None, " ",
TokenClassification.Comment, "<#123#>",
TokenClassification.None, " ",
TokenClassification.String, "\"hello ",
TokenClassification.Variable, "$name",
TokenClassification.String, "\"",
TokenClassification.None, " ",
TokenClassification.Number, "1",
TokenClassification.None, " + (",
TokenClassification.Number, "1",
TokenClassification.Operator, "-",
TokenClassification.Number, "2",
TokenClassification.None, ")")),
_.Ctrl_c,
InputAcceptedNow
));
// This tests for priority to highlight a command regardless of token kind and nested tokens potential to bleed the parent token color to the next token
Test("", Keys(
". -abc def;. abc$name -def",
_.Home,
CheckThat(() =>
AssertScreenIs(1,
TokenClassification.None, ". ",
TokenClassification.Command, "-abc",
TokenClassification.None, " def;. ",
TokenClassification.Command, "abc",
TokenClassification.Variable, "$name",
TokenClassification.None, " ",
TokenClassification.Parameter, "-def")),
_.Ctrl_c,
InputAcceptedNow
));
// Additional test for priority to highlight a command regardless of token kind and nested tokens potential to bleed the parent token color to the next token
Test("", Keys(
". ++ abc$name -def",
_.Home,
CheckThat(() =>
AssertScreenIs(1,
TokenClassification.None, ". ",
TokenClassification.Command, "++",
TokenClassification.None, " abc",
TokenClassification.Variable, "$name",
TokenClassification.None, " ",
TokenClassification.Parameter, "-def")),
_.Ctrl_c,
InputAcceptedNow
));
// test that rendering doesn't cause an exception in a potential missing "EOS" token case.
// this case could be a moving target, if the PowerShell parser is changed such as to eliminate the case.
Test("", Keys(
"process $abc\\name | def",
CheckThat(() =>
AssertScreenIs(1,
TokenClassification.Keyword, "process",
TokenClassification.None, " ",
TokenClassification.Variable, "$abc",
TokenClassification.None, "\\name | def")),
_.Ctrl_c,
InputAcceptedNow
));
Test("", Keys(
"process out put",
CheckThat(() =>
AssertScreenIs(1,
TokenClassification.Keyword, "process",
TokenClassification.None, " out put")),
_.Ctrl_c,
InputAcceptedNow
));
Test("", Keys(
"\"$([int];\"_$(1+2)\")\"",
CheckThat(() =>
AssertScreenIs(1,
TokenClassification.String, "\"",
TokenClassification.None, "$(",
TokenClassification.None, "[",
TokenClassification.Type, "int",
TokenClassification.None, "];",
TokenClassification.String, "\"_",
TokenClassification.None, "$(",
TokenClassification.Number, "1",
TokenClassification.Operator, "+",
TokenClassification.Number, "2",
TokenClassification.None, ")",
TokenClassification.String, "\"",
TokenClassification.None, ")",
TokenClassification.String, "\"")),
_.Ctrl_c,
InputAcceptedNow
));
Test("", Keys(
"\"a $b c $d e\"",
CheckThat(() =>
AssertScreenIs(1,
TokenClassification.String, "\"a ",
TokenClassification.Variable, "$b",
TokenClassification.String, " c ",
TokenClassification.Variable, "$d",
TokenClassification.String, " e\"")),
_.Ctrl_c,
InputAcceptedNow
));
Test("{}", Keys(
'{', _.Enter,
_.Backspace, CheckThat(() => AssertScreenIs(2, TokenClassification.None, '{', NextLine)),
'}'));
_console.Clear();
string promptLine = "PS> ";
Test("\"\"", Keys(
'"',
CheckThat(() => AssertScreenIs(1,
TokenClassification.None,
promptLine.Substring(0, promptLine.IndexOf('>')),
Tuple.Create(ConsoleColor.Red, ConsoleColor.DarkRed), "> ",
TokenClassification.String, "\"")),
'"'), prompt: promptLine);
}
[SkippableFact]
public void MultiLine()
{
TestSetup(KeyMode.Cmd);
Test("d|\nd", Keys(
"d|",
_.Enter, CheckThat(() => AssertCursorTopIs(1)),
'd'));
// Make sure <ENTER> when input is incomplete actually puts a newline
// wherever the cursor is.
Test("{\n\nd\n}", Keys(
'{',
_.Enter, CheckThat(() => AssertCursorTopIs(1)),
'd',
_.Enter, CheckThat(() => AssertCursorTopIs(2)),
_.Home,
_.RightArrow, CheckThat(() => AssertCursorLeftTopIs(1, 0)),
_.Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)),
_.End, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 3)),
'}'));
// Make sure <ENTER> when input successfully parses accepts the input regardless
// of where the cursor is, plus it moves the cursor to the end (so the new prompt
// doesn't overwrite the end of the previous long/multi-line command line.)
Test("{\n}", Keys(
"{\n}",
_.Home,
_.Enter, CheckThat(() => AssertCursorLeftTopIs(0, 2))));
}
[SkippableFact]
public void MultiLine_ScreenCheck()
{
Skip.If(ScreenReaderModeEnabled, "Continuation prompt is not supported in screen reader mode.");
TestSetup(KeyMode.Cmd);
var defaultContinuationPrompt = PSConsoleReadLineOptions.DefaultContinuationPrompt;
var defaultContinuationPromptColors = Tuple.Create(_console.ForegroundColor, _console.BackgroundColor);
Test("@'\n\n hello\n\n world\n'@",
Keys(
"@'", _.Enter, _.Enter,
" hello", _.Enter, _.Enter,
" world", _.Enter, "'@",
CheckThat(() => AssertScreenIs(6,
TokenClassification.String, "@'",
NextLine,
defaultContinuationPromptColors, defaultContinuationPrompt,
NextLine,
defaultContinuationPromptColors, defaultContinuationPrompt,
TokenClassification.String, " hello",
NextLine,
defaultContinuationPromptColors, defaultContinuationPrompt,
NextLine,
defaultContinuationPromptColors, defaultContinuationPrompt,
TokenClassification.String, " world",
NextLine,
defaultContinuationPromptColors, defaultContinuationPrompt,
TokenClassification.String, "'@"))
));
// Set the continuation prompt to be an empty string.
var setOption = new SetPSReadLineOption { ContinuationPrompt = string.Empty };
PSConsoleReadLine.SetOptions(setOption);
try
{
Test("@'\n\n hello\n\n world\n'@",
Keys(
"@'", _.Enter, _.Enter,
" hello", _.Enter, _.Enter,
" world", _.Enter, "'@",
CheckThat(() => AssertScreenIs(6,
TokenClassification.String, "@'",
NextLine,
NextLine,
TokenClassification.String, " hello",
NextLine,
NextLine,
TokenClassification.String, " world",
NextLine,
TokenClassification.String, "'@"))
));
}
finally
{
// Restore to the default continuation prompt.
setOption.ContinuationPrompt = defaultContinuationPrompt;
PSConsoleReadLine.SetOptions(setOption);
}
}
[SkippableFact]
public void LongLine()
{
TestSetup(KeyMode.Cmd);
var sb = new StringBuilder();
sb.Append('"');
sb.Append('z', _console.BufferWidth);
sb.Append('"');
var input = sb.ToString();
Test(input, Keys(input));
}
[SkippableFact]
public void InvokePrompt()
{
TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+z", PSConsoleReadLine.InvokePrompt));
// Test dumb prompt that doesn't return anything.
using (var ps = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
ps.AddScript(@"function prompt {}");
ps.Invoke();
}
Test("dir", Keys(
"dir", _.Ctrl_z,
CheckThat(() => AssertScreenIs(1,
Tuple.Create(_console.ForegroundColor, _console.BackgroundColor), "PS>",
TokenClassification.Command, "dir"))));
// Test a boring prompt function
using (var ps = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
ps.AddScript(@"function prompt { 'PSREADLINE> ' }");
ps.Invoke();
}
Test("dir", Keys(
"dir", _.Ctrl_z,
CheckThat(() => AssertScreenIs(1,
Tuple.Create(_console.ForegroundColor, _console.BackgroundColor), "PSREADLINE> ",
TokenClassification.Command, "dir"))));
}
[SkippableFact]
public void InvokeTrickyPrompt()
{
Skip.If(ScreenReaderModeEnabled, "We can't test the colors written in this prompt in screen reader mode.");
TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+z", PSConsoleReadLine.InvokePrompt));
// Tricky prompt - writes to console directly with colors, uses ^H trick to eliminate trailing space.
using (var ps = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
ps.AddCommand("New-Variable").AddParameter("Name", "__console").AddParameter("Value", _console).Invoke();
ps.Commands.Clear();
ps.AddScript(@"
function prompt {
$fg = $__console.ForegroundColor
$bg = $__console.BackgroundColor
$__console.ForegroundColor = [ConsoleColor]::Blue
$__console.BackgroundColor = [ConsoleColor]::Magenta
$__console.Write('PSREADLINE>')
$__console.ForegroundColor = $fg
$__console.BackgroundColor = $bg
return ' ' + ([char]8)
}");
ps.Invoke();
}
Test("dir", Keys(
"dir", _.Ctrl_z,
CheckThat(() => AssertScreenIs(1,
Tuple.Create(ConsoleColor.Blue, ConsoleColor.Magenta), "PSREADLINE>",
TokenClassification.Command, "dir"))));
}
}
}