utest.markdown.scanner.pas 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2025 by Michael Van Canneyt
  4. Markdown text scanner tests
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit UTest.Markdown.Scanner;
  12. {$mode objfpc}{$H+}
  13. interface
  14. uses
  15. Classes, SysUtils, fpcunit, testregistry,
  16. MarkDown.Elements, MarkDown.Scanner;
  17. type
  18. TMyMarkDownTextScanner = Class(TMarkDownTextScanner)
  19. Public
  20. Property Cursor;
  21. Property LineNo;
  22. end;
  23. { TTestMarkDownTextScanner }
  24. TTestMarkDownTextScanner = class(TTestCase)
  25. private
  26. FScanner: TMyMarkDownTextScanner;
  27. protected
  28. procedure InitScanner(const aText : string; aLineNo : Integer);
  29. procedure SetUp; override;
  30. procedure TearDown; override;
  31. property Scanner : TMyMarkDownTextScanner Read FScanner;
  32. published
  33. procedure TestCreate;
  34. procedure TestEOF;
  35. procedure TestPeek;
  36. procedure TestPeekNext;
  37. procedure TestPeekPrevious;
  38. procedure TestPeekEndRun;
  39. procedure TestNextChar;
  40. procedure TestLocation;
  41. procedure TestBookmark;
  42. procedure TestPeekLen;
  43. procedure TestPeekWhile;
  44. procedure TestPeekUntil;
  45. procedure TestPeekRun;
  46. procedure TestNextChars;
  47. procedure TestNextEquals;
  48. procedure TestHas;
  49. procedure TestSkipWhitespace;
  50. procedure TestFindMatchingOccurrence;
  51. procedure TestFindMatchingOccurrenceExclude;
  52. end;
  53. implementation
  54. { TTestMarkDownTextScanner }
  55. procedure TTestMarkDownTextScanner.InitScanner(const aText: string; aLineNo: Integer);
  56. begin
  57. FreeAndNil(FScanner);
  58. FScanner:=TMyMarkDownTextScanner.Create(aText, aLineNo);
  59. end;
  60. procedure TTestMarkDownTextScanner.SetUp;
  61. begin
  62. InitScanner('abcde', 1);
  63. end;
  64. procedure TTestMarkDownTextScanner.TearDown;
  65. begin
  66. FreeAndNil(FScanner);
  67. end;
  68. procedure TTestMarkDownTextScanner.TestCreate;
  69. begin
  70. AssertNotNull('Scanner should be created', FScanner);
  71. AssertEquals('Initial cursor should be 1', 1, FScanner.Cursor);
  72. AssertEquals('Initial line number should be set', 1, FScanner.LineNo);
  73. end;
  74. procedure TTestMarkDownTextScanner.TestEOF;
  75. begin
  76. // Test with the default scanner
  77. AssertFalse('Should not be EOF at start', FScanner.EOF);
  78. FScanner.NextChars(5);
  79. AssertTrue('Should be EOF after reading all chars', FScanner.EOF);
  80. // Test with an empty string
  81. InitScanner('', 1);
  82. AssertTrue('Empty scanner should be at EOF', Scanner.EOF);
  83. end;
  84. procedure TTestMarkDownTextScanner.TestPeek;
  85. begin
  86. AssertEquals('Peek should return first char', 'a', FScanner.Peek);
  87. FScanner.NextChar;
  88. AssertEquals('Peek should return second char', 'b', FScanner.Peek);
  89. FScanner.NextChars(4); // Move to end
  90. AssertTrue('Should be at EOF', FScanner.EOF);
  91. AssertEquals('Peek at EOF should return #0', #0, FScanner.Peek);
  92. end;
  93. procedure TTestMarkDownTextScanner.TestPeekNext;
  94. begin
  95. AssertEquals('PeekNext should see the second char', 'b', FScanner.PeekNext);
  96. FScanner.NextChars(3);
  97. AssertEquals('PeekNext should see the last char', 'e', FScanner.PeekNext);
  98. FScanner.NextChar;
  99. AssertEquals('PeekNext at the last char should be #0', #0, FScanner.PeekNext);
  100. FScanner.NextChar;
  101. AssertTrue('Should be at EOF', FScanner.EOF);
  102. AssertEquals('PeekNext at EOF should be #0', #0, FScanner.PeekNext);
  103. end;
  104. procedure TTestMarkDownTextScanner.TestPeekPrevious;
  105. begin
  106. AssertEquals('PeekPrevious at start should be #0', #0, FScanner.PeekPrevious);
  107. FScanner.NextChar;
  108. AssertEquals('PeekPrevious at second char should be "a"', 'a', FScanner.PeekPrevious);
  109. FScanner.NextChars(4);
  110. AssertEquals('PeekPrevious at EOF should be "e"', 'e', FScanner.PeekPrevious);
  111. end;
  112. procedure TTestMarkDownTextScanner.TestPeekEndRun;
  113. begin
  114. InitScanner('a--b-c', 1);
  115. AssertEquals('PeekEndRun on single char', '-', Scanner.PeekEndRun);
  116. Scanner.NextChar; // Cursor at '-'
  117. AssertEquals('PeekEndRun on run start', 'b', Scanner.PeekEndRun);
  118. Scanner.NextChars(3); // Cursor at '-'
  119. AssertEquals('PeekEndRun on single char before another', 'c', Scanner.PeekEndRun);
  120. Scanner.NextChars(2); // Cursor at EOF
  121. AssertEquals('PeekEndRun at EOF', #0, Scanner.PeekEndRun);
  122. end;
  123. procedure TTestMarkDownTextScanner.TestNextChar;
  124. begin
  125. AssertEquals('NextChar should return "a"', 'a', FScanner.NextChar);
  126. AssertEquals('Cursor should advance to 2', 2, FScanner.Cursor);
  127. AssertEquals('Peek should now be "b"', 'b', FScanner.Peek);
  128. FScanner.NextChars(4); // To EOF
  129. AssertEquals('NextChar at EOF should return #0', #0, FScanner.NextChar);
  130. end;
  131. procedure TTestMarkDownTextScanner.TestLocation;
  132. var
  133. Pos: TPosition;
  134. begin
  135. InitScanner('a'#10'b', 5); // Start at line 5
  136. Pos := Scanner.Location;
  137. AssertEquals('Initial line should be 5', 5, Pos.Line);
  138. AssertEquals('Initial col should be 1', 1, Pos.Col);
  139. Scanner.NextChar; // consume 'a'
  140. Pos := Scanner.Location;
  141. AssertEquals('Line should still be 5', 5, Pos.Line);
  142. AssertEquals('Col should be 2', 2, Pos.Col);
  143. Scanner.NextChar; // consume #10
  144. Pos := Scanner.Location;
  145. AssertEquals('Line should be 6 after newline', 6, Pos.Line);
  146. AssertEquals('Col should be 1 after newline', 1, Pos.Col);
  147. end;
  148. procedure TTestMarkDownTextScanner.TestBookmark;
  149. begin
  150. FScanner.NextChars(2); // Cursor at 'c'
  151. AssertEquals('Cursor should be at position 3', 3, FScanner.Cursor);
  152. FScanner.Bookmark;
  153. FScanner.NextChars(2); // Cursor at 'e'
  154. AssertEquals('Cursor should be at position 5', 5, FScanner.Cursor);
  155. FScanner.GotoBookmark;
  156. AssertEquals('Cursor should be back at position 3', 3, FScanner.Cursor);
  157. AssertEquals('Peek should be "c" after goto bookmark', 'c', FScanner.Peek);
  158. end;
  159. procedure TTestMarkDownTextScanner.TestPeekLen;
  160. begin
  161. AssertEquals('PeekLen(3) should be "abc"', 'abc', FScanner.PeekLen(3));
  162. AssertEquals('Cursor should not move after PeekLen', 1, FScanner.Cursor);
  163. FScanner.NextChars(3);
  164. AssertEquals('PeekLen(5) past EOF should be "de"', 'de', FScanner.PeekLen(5));
  165. AssertEquals('Cursor should not move after PeekLen past EOF', 4, FScanner.Cursor);
  166. end;
  167. procedure TTestMarkDownTextScanner.TestPeekWhile;
  168. begin
  169. AssertEquals('PeekWhile should get "ab"', 'ab', FScanner.PeekWhile(['a', 'b']));
  170. AssertEquals('Cursor should not move after PeekWhile', 1, FScanner.Cursor);
  171. AssertEquals('PeekWhile with no match should be empty', '', FScanner.PeekWhile(['x', 'y']));
  172. end;
  173. procedure TTestMarkDownTextScanner.TestPeekUntil;
  174. begin
  175. InitScanner('abc*def', 1);
  176. AssertEquals('PeekUntil should get "abc"', 'abc', Scanner.PeekUntil(['*']));
  177. AssertEquals('Cursor should not move after PeekUntil', 1, Scanner.Cursor);
  178. AssertEquals('PeekUntil with no match should be empty', '', Scanner.PeekUntil(['z']));
  179. end;
  180. procedure TTestMarkDownTextScanner.TestPeekRun;
  181. begin
  182. InitScanner('a***b', 1);
  183. Scanner.NextChar;
  184. AssertEquals('PeekRun should find "***"', '***', Scanner.PeekRun(False));
  185. AssertEquals('Cursor should not move after PeekRun', 2, Scanner.Cursor);
  186. // Test checkBefore
  187. AssertEquals('PeekRun with checkBefore=true and different prev char should succeed', '***', Scanner.PeekRun(True));
  188. Scanner.NextChar; // cursor at second '*'
  189. AssertEquals('PeekRun with checkBefore=true and same prev char should fail', '', Scanner.PeekRun(True));
  190. end;
  191. procedure TTestMarkDownTextScanner.TestNextChars;
  192. begin
  193. AssertEquals('NextChars(3) should return "abc"', 'abc', FScanner.NextChars(3));
  194. AssertEquals('Cursor should be at 4 after NextChars(3)', 4, FScanner.Cursor);
  195. AssertEquals('NextChars(5) past EOF should return "de"', 'de', FScanner.NextChars(5));
  196. AssertTrue('Should be at EOF after reading past end', FScanner.EOF);
  197. end;
  198. procedure TTestMarkDownTextScanner.TestNextEquals;
  199. begin
  200. InitScanner('---abc', 1);
  201. AssertEquals('NextEquals should consume "---"', '---', Scanner.NextEquals);
  202. AssertEquals('Cursor should be at 4 after NextEquals', 4, Scanner.Cursor);
  203. AssertEquals('Peek should be "a"', 'a', Scanner.Peek);
  204. end;
  205. procedure TTestMarkDownTextScanner.TestHas;
  206. begin
  207. AssertTrue('Has("ab") should be true at start', FScanner.Has('ab'));
  208. AssertFalse('Has("ac") should be false at start', FScanner.Has('ac'));
  209. AssertEquals('Cursor should not move after Has', 1, FScanner.Cursor);
  210. end;
  211. procedure TTestMarkDownTextScanner.TestSkipWhitespace;
  212. begin
  213. InitScanner(' ab', 1);
  214. Scanner.SkipWhitespace;
  215. AssertEquals('Cursor should be at 3 after skipping whitespace', 3, Scanner.Cursor);
  216. AssertEquals('Peek should be "a"', 'a', Scanner.Peek);
  217. end;
  218. procedure TTestMarkDownTextScanner.TestFindMatchingOccurrence;
  219. begin
  220. InitScanner('some **bold** text, not ***this***', 1);
  221. AssertTrue('Should find "**" in " **bold** "', Scanner.FindMatchingOccurrence('**'));
  222. Scanner.Cursor:=25;
  223. AssertFalse('Should not find "**" in "***this***" because of surrounding *', Scanner.FindMatchingOccurrence('**'));
  224. end;
  225. procedure TTestMarkDownTextScanner.TestFindMatchingOccurrenceExclude;
  226. begin
  227. InitScanner('a `code` and not a \`backtick`', 1);
  228. AssertTrue('Should find `code`', Scanner.FindMatchingOccurrence('`', '\'));
  229. Scanner.Cursor := 18; // Move cursor to "and not a "
  230. AssertFalse('Should not find `backtick` because it is escaped by \', Scanner.FindMatchingOccurrence('`', '\'));
  231. end;
  232. initialization
  233. RegisterTest(TTestMarkDownTextScanner);
  234. end.