OutputBaseTests.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. using System.Text;
  2. using Terminal.Gui.Drivers;
  3. namespace DriverTests;
  4. public class OutputBaseTests
  5. {
  6. [Fact]
  7. public void ToAnsi_SingleCell_NoAttribute_ReturnsGraphemeAndNewline ()
  8. {
  9. // Arrange
  10. var output = new FakeOutput ();
  11. IOutputBuffer buffer = output.GetLastBuffer ()!;
  12. buffer.SetSize (1, 1);
  13. // Act
  14. buffer.AddStr ("A");
  15. string ansi = output.ToAnsi (buffer);
  16. // Assert: single grapheme plus newline (BuildAnsiForRegion appends a newline per row)
  17. Assert.Contains ("A" + Environment.NewLine, ansi);
  18. }
  19. [Theory]
  20. [InlineData (true, false)]
  21. [InlineData (true, true)]
  22. [InlineData (false, false)]
  23. [InlineData (false, true)]
  24. public void ToAnsi_WithAttribute_AppendsCorrectColorSequence_BasedOnIsLegacyConsole_And_Force16Colors (bool isLegacyConsole, bool force16Colors)
  25. {
  26. // Arrange
  27. var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
  28. // Create DriverImpl and associate it with the FakeOutput to test Sixel output
  29. IDriver driver = new DriverImpl (
  30. new FakeInputProcessor (null!),
  31. new OutputBufferImpl (),
  32. output,
  33. new (new AnsiResponseParser ()),
  34. new SizeMonitorImpl (output));
  35. driver.Force16Colors = force16Colors;
  36. IOutputBuffer buffer = output.GetLastBuffer ()!;
  37. buffer.SetSize (1, 1);
  38. // Use a known RGB color and attribute
  39. var fg = new Color (1, 2, 3);
  40. var bg = new Color (4, 5, 6);
  41. buffer.CurrentAttribute = new (fg, bg);
  42. buffer.AddStr ("X");
  43. // Act
  44. string ansi = output.ToAnsi (buffer);
  45. // Assert: when true color expected, we should see the RGB CSI; otherwise we should see the 16-color CSI
  46. if (!isLegacyConsole && !force16Colors)
  47. {
  48. Assert.Contains ("\u001b[38;2;1;2;3m", ansi);
  49. }
  50. else if (!isLegacyConsole && force16Colors)
  51. {
  52. string expected16 = EscSeqUtils.CSI_SetForegroundColor (fg.GetAnsiColorCode ());
  53. Assert.Contains (expected16, ansi);
  54. }
  55. else
  56. {
  57. var expected16 = (ConsoleColor)fg.GetClosestNamedColor16 ();
  58. Assert.Equal (ConsoleColor.Black, expected16);
  59. Assert.DoesNotContain ('\u001b', ansi);
  60. }
  61. // Grapheme and newline should always be present
  62. Assert.Contains ("X" + Environment.NewLine, ansi);
  63. }
  64. [Fact]
  65. public void Write_WritesDirtyCellsAndClearsDirtyFlags ()
  66. {
  67. // Arrange
  68. var output = new FakeOutput ();
  69. IOutputBuffer buffer = output.GetLastBuffer ()!;
  70. buffer.SetSize (2, 1);
  71. // Mark two characters as dirty by writing them into the buffer
  72. buffer.AddStr ("AB");
  73. // Sanity: ensure cells are dirty before calling Write
  74. Assert.True (buffer.Contents! [0, 0].IsDirty);
  75. Assert.True (buffer.Contents! [0, 1].IsDirty);
  76. // Act
  77. output.Write (buffer); // calls OutputBase.Write via FakeOutput
  78. // Assert: content was written to the fake output and dirty flags cleared
  79. Assert.Contains ("AB", output.GetLastOutput ());
  80. Assert.False (buffer.Contents! [0, 0].IsDirty);
  81. Assert.False (buffer.Contents! [0, 1].IsDirty);
  82. }
  83. [Theory]
  84. [InlineData (true)]
  85. [InlineData (false)]
  86. public void Write_Virtual_Or_NonVirtual_Uses_WriteToConsole_And_Clears_Dirty_Flags (bool isLegacyConsole)
  87. {
  88. // Arrange
  89. // FakeOutput exposes this because it's in test scope
  90. var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
  91. IOutputBuffer buffer = output.GetLastBuffer ()!;
  92. buffer.SetSize (3, 1);
  93. // Write 'A' at col 0 and 'C' at col 2; leave col 1 untouched (not dirty)
  94. buffer.Move (0, 0);
  95. buffer.AddStr ("A");
  96. buffer.Move (2, 0);
  97. buffer.AddStr ("C");
  98. // Confirm some dirtiness before to write
  99. Assert.True (buffer.Contents! [0, 0].IsDirty);
  100. Assert.True (buffer.Contents! [0, 2].IsDirty);
  101. // Act
  102. output.Write (buffer);
  103. // Assert: both characters were written (use Contains to avoid CI side effects)
  104. Assert.Contains ("A", output.GetLastOutput ());
  105. Assert.Contains ("C", output.GetLastOutput ());
  106. // Dirty flags cleared for the written cells
  107. Assert.False (buffer.Contents! [0, 0].IsDirty);
  108. Assert.False (buffer.Contents! [0, 2].IsDirty);
  109. // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
  110. Assert.Equal (new (0, 0), output.GetCursorPosition ());
  111. // Now write 'X' at col 0 to verify subsequent writes also work
  112. buffer.Move (0, 0);
  113. buffer.AddStr ("X");
  114. // Confirm dirtiness state before to write
  115. Assert.True (buffer.Contents! [0, 0].IsDirty);
  116. Assert.False (buffer.Contents! [0, 2].IsDirty);
  117. output.Write (buffer);
  118. // Assert: both characters were written (use Contains to avoid CI side effects)
  119. Assert.Contains ("A", output.GetLastOutput ());
  120. Assert.Contains ("C", output.GetLastOutput ());
  121. // Dirty flags cleared for the written cells
  122. Assert.False (buffer.Contents! [0, 0].IsDirty);
  123. Assert.False (buffer.Contents! [0, 2].IsDirty);
  124. // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
  125. Assert.Equal (new (2, 0), output.GetCursorPosition ());
  126. }
  127. [Theory]
  128. [InlineData (true)]
  129. [InlineData (false)]
  130. public void Write_Virtual_Or_NonVirtual_Uses_WriteToConsole_And_Clears_Dirty_Flags_Mixed_Graphemes (bool isLegacyConsole)
  131. {
  132. // Arrange
  133. // FakeOutput exposes this because it's in test scope
  134. var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
  135. IOutputBuffer buffer = output.GetLastBuffer ()!;
  136. buffer.SetWideGlyphReplacement ((Rune)'①');
  137. buffer.SetSize (3, 1);
  138. // Write '🦮' at col 0 and 'A' at col 2
  139. buffer.Move (0, 0);
  140. buffer.AddStr ("🦮A");
  141. // After the fix for https://github.com/gui-cs/Terminal.Gui/issues/4258:
  142. // Writing a wide glyph at column 0 no longer sets column 1 to IsDirty = false.
  143. // Column 1 retains whatever state it had (in this case, it was initialized as dirty
  144. // by ClearContents, but may have been cleared by a previous Write call).
  145. //
  146. // What we care about is that wide glyphs work correctly and don't prevent
  147. // other content from being drawn at odd columns.
  148. Assert.True (buffer.Contents! [0, 0].IsDirty);
  149. // Column 1 state depends on whether it was cleared by a previous Write - don't assert
  150. Assert.True (buffer.Contents! [0, 2].IsDirty);
  151. // Act
  152. output.Write (buffer);
  153. Assert.Contains ("🦮", output.GetLastOutput ());
  154. Assert.Contains ("A", output.GetLastOutput ());
  155. // Dirty flags cleared for the written cells
  156. // Column 0 was written (wide glyph)
  157. Assert.False (buffer.Contents! [0, 0].IsDirty);
  158. // Column 1 was marked as clean by OutputBase.Write when it processed the wide glyph at column 0
  159. // See: https://github.com/gui-cs/Terminal.Gui/issues/4466
  160. Assert.False (buffer.Contents! [0, 1].IsDirty);
  161. // Column 2 was written ('A')
  162. Assert.False (buffer.Contents! [0, 2].IsDirty);
  163. Assert.Equal (new (0, 0), output.GetCursorPosition ());
  164. // Now write 'X' at col 1 which invalidates the wide glyph at col 0
  165. buffer.Move (1, 0);
  166. buffer.AddStr ("X");
  167. // Confirm dirtiness state before to write
  168. Assert.True (buffer.Contents! [0, 0].IsDirty); // Invalidated by writing at col 1
  169. Assert.True (buffer.Contents! [0, 1].IsDirty); // Just written
  170. Assert.True (buffer.Contents! [0, 2].IsDirty); // Marked dirty by writing at col 1
  171. output.Write (buffer);
  172. Assert.Contains ("①", output.GetLastOutput ());
  173. Assert.Contains ("X", output.GetLastOutput ());
  174. // Dirty flags cleared for the written cells
  175. Assert.False (buffer.Contents! [0, 0].IsDirty);
  176. Assert.False (buffer.Contents! [0, 1].IsDirty);
  177. Assert.False (buffer.Contents! [0, 2].IsDirty);
  178. // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
  179. Assert.Equal (new (0, 0), output.GetCursorPosition ());
  180. }
  181. [Theory]
  182. [InlineData (true)]
  183. [InlineData (false)]
  184. public void Write_EmitsSixelDataAndPositionsCursor (bool isLegacyConsole)
  185. {
  186. // Arrange
  187. var output = new FakeOutput ();
  188. IOutputBuffer buffer = output.GetLastBuffer ()!;
  189. buffer.SetSize (1, 1);
  190. // Ensure the buffer has some content so Write traverses rows
  191. buffer.AddStr (".");
  192. // Create a Sixel to render
  193. var s = new SixelToRender
  194. {
  195. SixelData = "SIXEL-DATA",
  196. ScreenPosition = new (4, 2)
  197. };
  198. // Create DriverImpl and associate it with the FakeOutput to test Sixel output
  199. IDriver driver = new DriverImpl (
  200. new FakeInputProcessor (null!),
  201. new OutputBufferImpl (),
  202. output,
  203. new (new AnsiResponseParser ()),
  204. new SizeMonitorImpl (output));
  205. // Add the Sixel to the driver
  206. driver.GetSixels ().Enqueue (s);
  207. // FakeOutput exposes this because it's in test scope
  208. output.IsLegacyConsole = isLegacyConsole;
  209. // Act
  210. output.Write (buffer);
  211. if (!isLegacyConsole)
  212. {
  213. // Assert: Sixel data was emitted (use Contains to avoid equality/side-effects)
  214. Assert.Contains ("SIXEL-DATA", output.GetLastOutput ());
  215. // Cursor was moved to Sixel position
  216. Assert.Equal (s.ScreenPosition, output.GetCursorPosition ());
  217. }
  218. else
  219. {
  220. // Assert: Sixel data was NOT emitted
  221. Assert.DoesNotContain ("SIXEL-DATA", output.GetLastOutput ());
  222. // Cursor was NOT moved to Sixel position
  223. Assert.NotEqual (s.ScreenPosition, output.GetCursorPosition ());
  224. }
  225. IApplication app = Application.Create ();
  226. app.Driver = driver;
  227. Assert.Equal (driver.GetSixels (), app.Driver.GetSixels ());
  228. app.Dispose ();
  229. }
  230. }