Files
ttui/test/TinyTUI.Tests/Rendering/DifferentialRendererTests.cs
T
chuan c435df9fcd
ci / restore build format test pack (push) Failing after 22s
test: converge smoke checks into xunit suite
- migrate remaining text and component smoke assertions into TinyTUI.Tests

- remove legacy TextChecks and ComponentChecks projects from solution

- update README test guidance to use the unified xUnit suite
2026-06-04 10:15:57 +08:00

120 lines
4.3 KiB
C#

using TinyTUI.Rendering;
using TinyTUI.Terminal.Images;
using TinyTUI.Tests.TestInfrastructure;
using TinyTUI.Text;
namespace TinyTUI.Tests.Rendering;
public sealed class DifferentialRendererTests
{
private static readonly RenderPipelineOptions TestOptions = new()
{
ThrowOnWidthOverflow = true,
UseSynchronizedOutput = false,
};
[Fact]
public void FirstRenderCapturesVisibleViewportAndLineResets()
{
var output = new VirtualTerminalOutput(20, 3);
var renderer = CreateRenderer(output);
renderer.Render(["Line 0", "Line 1", "Line 2", "Line 3"], output.Size);
Assert.Equal(1, renderer.FullRedrawCount);
Assert.Equal(new RenderViewportState(1, 3, 4, 4), renderer.ViewportState);
Assert.Contains("Line 1\e[0m\e]8;;\a", output.Buffer.ToString(), StringComparison.Ordinal);
Assert.DoesNotContain("Line 0", output.Buffer.ToString(), StringComparison.Ordinal);
}
[Fact]
public void DiffRenderOnlyMovesAndClearsChangedRows()
{
var output = new VirtualTerminalOutput(20, 4);
var renderer = CreateRenderer(output);
renderer.Render(["Header", "Working...", "Footer"], output.Size);
output.ClearWrites();
renderer.Render(["Header", "Working /", "Footer"], output.Size);
Assert.Equal(1, renderer.DifferentialRedrawCount);
Assert.Contains("\e[2;1H\e[2KWorking /", output.Buffer.ToString(), StringComparison.Ordinal);
Assert.DoesNotContain("\e[1;1H\e[2KHeader", output.Buffer.ToString(), StringComparison.Ordinal);
}
[Fact]
public void ResizeAndShrinkForceFullRedrawsToClearStaleRows()
{
var output = new VirtualTerminalOutput(20, 5);
var renderer = CreateRenderer(output);
renderer.Render(["Line 0", "Line 1", "Line 2", "Line 3"], output.Size);
output.Resize(20, 6);
output.ClearWrites();
renderer.Render(["Line 0", "Line 1", "Line 2", "Line 3"], output.Size);
Assert.Equal(2, renderer.FullRedrawCount);
Assert.True(output.Contains("\e[2J\e[H"));
output.ClearWrites();
renderer.Render(["Line 0"], output.Size);
Assert.Equal(3, renderer.FullRedrawCount);
Assert.True(output.Contains("\e[2J\e[H"));
}
[Fact]
public void CursorMarkerMovesHardwareCursorAndCanShowCursor()
{
var output = new VirtualTerminalOutput(20, 4);
var renderer = new DifferentialRenderer(
output,
new TerminalTextMeasurer(),
new RenderPipelineOptions
{
ThrowOnWidthOverflow = true,
UseSynchronizedOutput = false,
ShowHardwareCursor = true,
});
renderer.Render([$"ab{CursorMarker.Marker}cd"], output.Size);
Assert.True(output.Contains("\e[1;3H"));
Assert.True(output.Contains("\e[?25h"));
}
[Fact]
public void ChangedKittyImageIsDeletedBeforeReplacementLine()
{
var output = new VirtualTerminalOutput(20, 4);
var renderer = CreateRenderer(output);
var kittyLine = "\e_Ga=T,f=100,q=2,C=1,c=1,r=1,i=42;AAAA\e\\";
renderer.Render([kittyLine], output.Size);
output.ClearWrites();
renderer.Render(["changed"], output.Size);
var writes = output.Buffer.ToString();
var deleteIndex = writes.IndexOf(TerminalImageService.DeleteKittyImage(42), StringComparison.Ordinal);
var changedIndex = writes.IndexOf("changed", StringComparison.Ordinal);
Assert.True(deleteIndex >= 0);
Assert.True(changedIndex >= 0);
Assert.True(deleteIndex < changedIndex);
}
[Fact]
public void LongITerm2ImageLineBypassesTextWidthOverflow()
{
var output = new VirtualTerminalOutput(20, 5);
var renderer = CreateRenderer(output);
var imageLine = $"Read image file \e]1337;File=inline=1:{new string('A', 200)}\a";
// 图像协议本身可能很长 但渲染器应按图像行处理而不是按可见文本宽度报错
renderer.Render([imageLine], output.Size);
renderer.Render(["plain"], output.Size);
Assert.Contains("plain", output.Buffer.ToString(), StringComparison.Ordinal);
}
private static DifferentialRenderer CreateRenderer(VirtualTerminalOutput output)
=> new(output, new TerminalTextMeasurer(), TestOptions);
}