SixelSupportDetectorTests.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #nullable enable
  2. using Moq;
  3. namespace DrawingTests;
  4. public class SixelSupportDetectorTests
  5. {
  6. [Fact]
  7. public void Detect_SetsSupportedAndResolution_WhenDeviceAttributesContain4_AndResolutionResponds()
  8. {
  9. // Arrange
  10. Mock<IDriver> driverMock = new (MockBehavior.Strict);
  11. // Setup IsLegacyConsole - false means modern terminal with ANSI support
  12. driverMock.Setup (d => d.IsLegacyConsole).Returns (false);
  13. // Expect QueueAnsiRequest to be called at least twice:
  14. // 1) CSI_SendDeviceAttributes (terminator "c")
  15. // 2) CSI_RequestSixelResolution (terminator "t")
  16. driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
  17. .Callback<AnsiEscapeSequenceRequest> (req =>
  18. {
  19. // Respond to the SendDeviceAttributes request with a value that indicates support (contains "4")
  20. if (req.Request == EscSeqUtils.CSI_SendDeviceAttributes.Request)
  21. {
  22. req.ResponseReceived.Invoke ("1;4;7c");
  23. }
  24. else if (req.Request == EscSeqUtils.CSI_RequestSixelResolution.Request)
  25. {
  26. // Reply with a resolution response matching regex "\[\d+;(\d+);(\d+)t$"
  27. // Group 1 -> ry, Group 2 -> rx. The detector constructs resolution as new(rx, ry)
  28. req.ResponseReceived.Invoke ("[6;20;10t");
  29. }
  30. else
  31. {
  32. // Any other request - call abandoned to avoid hanging
  33. req.Abandoned?.Invoke ();
  34. }
  35. })
  36. .Verifiable ();
  37. var detector = new SixelSupportDetector (driverMock.Object);
  38. SixelSupportResult? final = null;
  39. // Act
  40. detector.Detect (r => final = r);
  41. // Assert
  42. Assert.NotNull (final);
  43. Assert.True (final.IsSupported); // Response contained "4"
  44. // Resolution should be constructed as new(rx, ry) where rx=10, ry=20 from our reply "[6;20;10t"
  45. Assert.Equal (10, final.Resolution.Width);
  46. Assert.Equal (20, final.Resolution.Height);
  47. driverMock.Verify (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()), Times.AtLeast (2));
  48. }
  49. [Fact]
  50. public void Detect_DoesNotSetSupported_WhenDeviceAttributesDoNotContain4()
  51. {
  52. // Arrange
  53. var driverMock = new Mock<IDriver>(MockBehavior.Strict);
  54. // Setup IsLegacyConsole - false means modern terminal with ANSI support
  55. driverMock.Setup (d => d.IsLegacyConsole).Returns (false);
  56. driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
  57. .Callback<AnsiEscapeSequenceRequest> (req =>
  58. {
  59. // SendDeviceAttributes -> reply without "4"
  60. if (req.Request == EscSeqUtils.CSI_SendDeviceAttributes.Request)
  61. {
  62. req.ResponseReceived.Invoke ("1;0;7c");
  63. }
  64. else
  65. {
  66. // Any other requests should be abandoned
  67. req.Abandoned?.Invoke ();
  68. }
  69. })
  70. .Verifiable ();
  71. var detector = new SixelSupportDetector (driverMock.Object);
  72. SixelSupportResult? final = null;
  73. // Act
  74. detector.Detect (r => final = r);
  75. // Assert
  76. Assert.NotNull (final);
  77. Assert.False (final.IsSupported);
  78. // On no support, the direct resolution request path isn't followed so resolution remains the default
  79. Assert.Equal (10, final.Resolution.Width);
  80. Assert.Equal (20, final.Resolution.Height);
  81. driverMock.Verify (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()), Times.AtLeast (1));
  82. }
  83. [Theory]
  84. [InlineData (true)]
  85. [InlineData (false)]
  86. public void Detect_SetsSupported_WhenIsLegacyConsoleIsFalseAndResponseContain4OrFalse (bool isLegacyConsole)
  87. {
  88. // Arrange
  89. var responseReceived = false;
  90. var output = new FakeOutput ();
  91. output.IsLegacyConsole = isLegacyConsole;
  92. Mock<DriverImpl> driverMock = new (
  93. MockBehavior.Strict,
  94. new FakeInputProcessor (null!),
  95. new OutputBufferImpl (),
  96. output,
  97. new AnsiRequestScheduler (new AnsiResponseParser ()),
  98. new SizeMonitorImpl (output)
  99. );
  100. driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
  101. .Callback<AnsiEscapeSequenceRequest> (req =>
  102. {
  103. if (req.Request == EscSeqUtils.CSI_SendDeviceAttributes.Request)
  104. {
  105. responseReceived = true;
  106. if (!isLegacyConsole)
  107. {
  108. // Response does contain "4" (so DAR indicates has sixel)
  109. req.ResponseReceived.Invoke ("?1;4;0;7c");
  110. }
  111. else
  112. {
  113. // Response does NOT contain "4" (so DAR indicates no sixel)
  114. req.ResponseReceived.Invoke ("");
  115. }
  116. }
  117. else
  118. {
  119. // Abandon all requests
  120. req.Abandoned?.Invoke ();
  121. }
  122. })
  123. .Verifiable ();
  124. var detector = new SixelSupportDetector (driverMock.Object);
  125. SixelSupportResult? final = null;
  126. // Act
  127. detector.Detect (r => final = r);
  128. // Assert
  129. Assert.Equal (isLegacyConsole, driverMock.Object.IsLegacyConsole);
  130. Assert.NotNull (final);
  131. if (!isLegacyConsole)
  132. {
  133. Assert.True (final.IsSupported);
  134. }
  135. else
  136. {
  137. // Not a real VT, so should be supported
  138. Assert.False (final.IsSupported);
  139. }
  140. Assert.True (responseReceived);
  141. driverMock.Verify (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()), Times.AtLeast (1));
  142. }
  143. [Theory]
  144. [InlineData (true)]
  145. [InlineData (false)]
  146. public void Detect_SetsSupported_WhenIsLegacyConsoleIsTrueOrFalse_With_Response (bool isLegacyConsole)
  147. {
  148. // Arrange
  149. var responseReceived = false;
  150. var output = new FakeOutput ();
  151. output.IsLegacyConsole = isLegacyConsole;
  152. Mock<DriverImpl> driverMock = new (
  153. MockBehavior.Strict,
  154. new FakeInputProcessor (null!),
  155. new OutputBufferImpl (),
  156. output,
  157. new AnsiRequestScheduler (new AnsiResponseParser ()),
  158. new SizeMonitorImpl (output)
  159. );
  160. driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
  161. .Callback<AnsiEscapeSequenceRequest> (req =>
  162. {
  163. if (req.Request == EscSeqUtils.CSI_SendDeviceAttributes.Request)
  164. {
  165. responseReceived = true;
  166. // Respond to the SendDeviceAttributes request with a value that indicates support (contains "4")
  167. // Respond to the SendDeviceAttributes request with an empty value that indicates non-support
  168. req.ResponseReceived.Invoke (!driverMock.Object.IsLegacyConsole ? "1;4;7c" : "");
  169. }
  170. // Abandon all requests
  171. req.Abandoned?.Invoke ();
  172. })
  173. .Verifiable ();
  174. var detector = new SixelSupportDetector (driverMock.Object);
  175. SixelSupportResult? final = null;
  176. // Act
  177. detector.Detect (r => final = r);
  178. // Assert
  179. Assert.Equal (isLegacyConsole, driverMock.Object.IsLegacyConsole);
  180. Assert.NotNull (final);
  181. if (!isLegacyConsole)
  182. {
  183. Assert.True (final.IsSupported);
  184. Assert.True (final.SupportsTransparency);
  185. }
  186. else
  187. {
  188. // Not a real VT, so shouldn't be supported
  189. Assert.False (final.IsSupported);
  190. Assert.False (final.SupportsTransparency);
  191. }
  192. Assert.True (responseReceived);
  193. }
  194. }