UrlHyperlinkerTests.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #nullable enable
  2. using System.Text;
  3. using Xunit.Abstractions;
  4. namespace Terminal.Gui.DriverTests;
  5. public class Osc8UrlLinkerTests (ITestOutputHelper output)
  6. {
  7. private readonly ITestOutputHelper _output = output;
  8. [Theory]
  9. [InlineData ("<https://example.com>", "<", "https://example.com", ">")]
  10. [InlineData ("\"https://example.com\"", "\"", "https://example.com", "\"")]
  11. public void WrapOsc8_Does_Not_Cross_Delimiters (string input, string prefix, string url, string suffix)
  12. {
  13. string actual = Wrap (input);
  14. string expected = prefix + LinkWrappedWithOSCs (url) + suffix;
  15. Assert.Equal (expected, actual);
  16. }
  17. [Theory]
  18. [InlineData ("No url here")]
  19. [InlineData ("http://")]
  20. [InlineData ("://missing-scheme.com")]
  21. public void WrapOsc8_Leaves_Text_Unchanged_Without_Urls (string input)
  22. {
  23. StringBuilder sb = new (input);
  24. StringBuilder result = Osc8UrlLinker.WrapOsc8 (sb);
  25. Assert.Same (sb, result);
  26. Assert.Equal (input, result.ToString ());
  27. }
  28. [Fact]
  29. public void WrapOsc8_Stops_At_Ansi_Escape_Sequence ()
  30. {
  31. var esc = "\x1B[38;2;173m";
  32. string input = "https://example.com" + esc + "/more";
  33. string wrapped = Wrap (input);
  34. string expected = LinkWrappedWithOSCs ("https://example.com") + esc + "/more";
  35. Assert.Equal (expected, wrapped);
  36. // Verify that the hyperlinked visible content contains no ESC
  37. string start = EscSeqUtils.OSC_StartHyperlink ("https://example.com");
  38. string end = EscSeqUtils.OSC_EndHyperlink ();
  39. int s = wrapped.IndexOf (start, StringComparison.Ordinal);
  40. Assert.True (s >= 0);
  41. int e = wrapped.IndexOf (end, s + start.Length, StringComparison.Ordinal);
  42. Assert.True (e > s);
  43. int contentStart = s + start.Length;
  44. int contentLength = e - contentStart;
  45. string visible = wrapped.Substring (contentStart, contentLength);
  46. Assert.DoesNotContain ('\x1B', visible);
  47. Assert.Equal ("https://example.com", visible);
  48. }
  49. [Theory]
  50. [InlineData ("See https://example.com/path.", "See ", "https://example.com/path", ".")]
  51. [InlineData ("See https://example.com/file.tar.gz,", "See ", "https://example.com/file.tar.gz", ",")]
  52. [InlineData ("See https://example.com/path_(with)_parens)", "See ", "https://example.com/path_(with)_parens", ")")]
  53. public void WrapOsc8_Trims_Trailing_Punctuation (string input, string prefix, string url, string suffix)
  54. {
  55. string actual = Wrap (input);
  56. string expected = prefix + LinkWrappedWithOSCs (url) + suffix;
  57. Assert.Equal (expected, actual);
  58. }
  59. [Theory]
  60. [InlineData (
  61. "Multiple: https://a.com, https://b.com!",
  62. "Multiple: ",
  63. "https://a.com",
  64. ", ",
  65. "https://b.com",
  66. "!")]
  67. [InlineData (
  68. "List https://one.com https://two.com https://three.com",
  69. "List ",
  70. "https://one.com",
  71. " ",
  72. "https://two.com",
  73. " ",
  74. "https://three.com",
  75. "")]
  76. public void WrapOsc8_Wraps_Multiple_Urls (string input, params string [] segments)
  77. {
  78. // segments: text0, url1, text1, [url2, text2]...
  79. string expected = BuildExpected (segments);
  80. string actual = Wrap (input);
  81. Assert.Equal (expected, actual);
  82. }
  83. [Theory]
  84. [InlineData ("https://github.com", "https://github.com")]
  85. [InlineData ("http://example.com", "http://example.com")]
  86. [InlineData ("ftp://ftp.example.com/file.zip", "ftp://ftp.example.com/file.zip")]
  87. [InlineData ("https://example.com:8080/path", "https://example.com:8080/path")]
  88. [InlineData ("https://example.com/path#anchor", "https://example.com/path#anchor")]
  89. [InlineData ("https://example.com/path?query=value&other=123", "https://example.com/path?query=value&other=123")]
  90. [InlineData ("https://commons.wikimedia.org/wiki/File:Spinning_globe.gif", "https://commons.wikimedia.org/wiki/File:Spinning_globe.gif")]
  91. public void WrapOsc8_Wraps_Standalone_Url (string input, string expectedUrl)
  92. {
  93. string actual = Wrap (input);
  94. string expected = LinkWrappedWithOSCs (expectedUrl);
  95. Assert.Equal (expected, actual);
  96. }
  97. [Theory]
  98. [InlineData (
  99. "Visit https://github.com for more info",
  100. "Visit ",
  101. "https://github.com",
  102. " for more info")]
  103. [InlineData (
  104. "Check out https://commons.wikimedia.org/wiki/File:Spinning_globe.gif!",
  105. "Check out ",
  106. "https://commons.wikimedia.org/wiki/File:Spinning_globe.gif",
  107. "!")]
  108. [InlineData (
  109. "URLs: https://example.com and http://other.com",
  110. "URLs: ",
  111. "https://example.com",
  112. " and ",
  113. "http://other.com",
  114. "")]
  115. public void WrapOsc8_Wraps_Urls_In_Text (string input, params string [] segments)
  116. {
  117. // segments: text0, url1, text1, [url2, text2]...
  118. string expected = BuildExpected (segments);
  119. string actual = Wrap (input);
  120. Assert.Equal (expected, actual);
  121. }
  122. private static string BuildExpected (string [] segments)
  123. {
  124. // segments must be: text0, url1, text1, url2, text2, ...
  125. string expected = segments.Length > 0 ? segments [0] : string.Empty;
  126. for (var i = 1; i + 1 < segments.Length; i += 2)
  127. {
  128. string url = segments [i];
  129. string textAfter = segments [i + 1];
  130. expected += LinkWrappedWithOSCs (url);
  131. expected += textAfter;
  132. }
  133. return expected;
  134. }
  135. private static string LinkWrappedWithOSCs (string url, string? display = null)
  136. {
  137. display ??= url;
  138. return EscSeqUtils.OSC_StartHyperlink (url) + display + EscSeqUtils.OSC_EndHyperlink ();
  139. }
  140. private static string Wrap (string input)
  141. {
  142. StringBuilder sb = new (input);
  143. return Osc8UrlLinker.WrapOsc8 (sb).ToString ();
  144. }
  145. }