using TinyTUI.Autocomplete; using TinyTUI.Components; using TinyTUI.Input; using TinyTUI.Rendering; using TinyTUI.Terminal.Images; using TinyTUI.Text; namespace TinyTUI.Tests.Components; public sealed class ComponentRegressionTests { private readonly TerminalTextMeasurer _measurer = new(); [Fact] public void FocusableComponentsOnlyRenderCursorMarkerWhenFocused() { var input = new TinyTUI.Components.Input(_measurer) { Prompt = "> " }; Assert.DoesNotContain(CursorMarker.Marker, input.Render(20)[0], StringComparison.Ordinal); input.Focused = true; Assert.Contains(CursorMarker.Marker, input.Render(20)[0], StringComparison.Ordinal); var editor = new Editor(_measurer) { Placeholder = "type here" }; Assert.DoesNotContain(CursorMarker.Marker, editor.Render(20)[0], StringComparison.Ordinal); editor.Focused = true; Assert.Contains(CursorMarker.Marker, editor.Render(20)[0], StringComparison.Ordinal); var list = new SelectList(_measurer); list.SetItems(["alpha", "beta"]); Assert.DoesNotContain(CursorMarker.Marker, list.Render(20)[0], StringComparison.Ordinal); list.Focused = true; Assert.Contains(CursorMarker.Marker, list.Render(20)[0], StringComparison.Ordinal); } [Fact] public void BasicLayoutComponentsClampAndPadRenderedLines() { Assert.Equal(3, new Spacer(3).Render(10).Count); Assert.Empty(new Spacer(-1).Render(10)); var truncated = new TruncatedText("abcdef\nignored", _measurer) { PaddingX = 1, PaddingY = 1, }; var lines = truncated.Render(6); Assert.Equal(3, lines.Count); Assert.Equal(6, _measurer.GetWidth(lines[1])); // TruncatedText 是单行组件 这里确认换行后的内容不会泄漏到渲染结果 Assert.Contains("abc", lines[1], StringComparison.Ordinal); Assert.DoesNotContain("ignored", string.Concat(lines), StringComparison.Ordinal); } [Fact] public void CancellableLoaderCancelsTokenAndRunsCallback() { using var loader = new CancellableLoader(keybindings: KeybindingRegistry.CreateDefault()); var canceled = false; loader.OnCanceled = () => canceled = true; loader.HandleInput(new TuiInputEvent(TuiInputEventKind.Key, KeyNames.Escape)); Assert.True(loader.IsCanceled); Assert.True(loader.Token.IsCancellationRequested); Assert.True(canceled); } [Fact] public void SlashCommandAutocompleteCanRenderConfirmAndCancel() { var editor = new Editor(_measurer) { AutocompleteProvider = new SlashCommandAutocompleteProvider( [ new SlashCommand("help", "show help"), new SlashCommand("history", "show history"), ]), }; editor.HandleInput(new TuiInputEvent(TuiInputEventKind.Text, "/h")); Assert.True(editor.IsAutocompleteActive); Assert.Contains(editor.RenderAutocomplete(40), line => line.Contains("help", StringComparison.Ordinal)); editor.HandleInput(new TuiInputEvent(TuiInputEventKind.Key, KeyNames.Down)); editor.HandleInput(new TuiInputEvent(TuiInputEventKind.Key, KeyNames.Tab)); Assert.Equal("/history ", editor.Value); Assert.False(editor.IsAutocompleteActive); editor.Value = "/h"; editor.HandleInput(new TuiInputEvent(TuiInputEventKind.Key, KeyNames.Tab)); editor.HandleInput(new TuiInputEvent(TuiInputEventKind.Key, KeyNames.Escape)); Assert.False(editor.IsAutocompleteActive); Assert.Equal("/h", editor.Value); } [Fact] public void ImageComponentRendersKittySequenceAndFallback() { var imageService = new TerminalImageService(); imageService.SetCapabilities(new TerminalCapabilities(ImageProtocol.Kitty, true, true)); imageService.SetCellDimensions(new CellDimensions(10, 10)); Assert.Equal(new ImageCellSize(2, 2), imageService.CalculateCellSize(new ImageDimensions(20, 20), 2)); Assert.Equal(new ImageDimensions(2, 3), imageService.TryGetDimensions(Png2x3, "image/png")); var image = new Image([1, 2, 3, 4], "image/png", imageService: imageService, dimensions: new ImageDimensions(20, 20)) { MaxWidthCells = 2, }; var imageLines = image.Render(4); Assert.Equal(2, imageLines.Count); Assert.True(image.ImageId is > 0); Assert.StartsWith("\e_G", imageLines[0], StringComparison.Ordinal); Assert.Contains(",C=1,", imageLines[0], StringComparison.Ordinal); Assert.True(TerminalImageService.IsImageLine($"prefix {imageLines[0]} suffix")); imageService.SetCapabilities(TerminalCapabilities.Conservative); var fallback = new Image([1, 2, 3], "image/jpeg", imageService: imageService, dimensions: new ImageDimensions(8, 9)) { FileName = "photo.jpg", }.Render(80)[0]; Assert.Contains("[Image: photo.jpg [image/jpeg] 8x9]", fallback, StringComparison.Ordinal); } private static readonly byte[] Png2x3 = Convert.FromBase64String( "iVBORw0KGgoAAAANSUhEUgAAAAIAAAADCAIAAADZrBkAAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAFElEQVR4nGNgYGBgYGBgYGBgAAAABQABJzQnCgAAAABJRU5ErkJggg=="); }