utest.markdown.inlinetext.pas 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2025 by Michael Van Canneyt
  4. Markdown Inline text processing 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.InlineText;
  12. {$mode objfpc}{$H+}
  13. interface
  14. uses
  15. Classes, SysUtils, fpcunit, testregistry, contnrs,
  16. Markdown.Elements, Markdown.Scanner, Markdown.InlineText;
  17. type
  18. { TTestInlineTextProcessor }
  19. TTestInlineTextProcessor = class(TTestCase)
  20. private
  21. FScanner: TMarkDownTextScanner;
  22. FNodes: TMarkDownTextNodeList;
  23. FProcessor: TInlineTextProcessor;
  24. FEntities: TFPStringHashTable;
  25. procedure DumpNodes;
  26. procedure SetupProcessor(const AText: AnsiString; awsMode: TWhitespaceMode = wsTrim);
  27. function NodeAsText(AIndex: Integer): TMarkDownTextNode;
  28. function NodeAsNamed(AIndex: Integer; AExpectedKind: TTextNodeKind): TMarkDownTextNode;
  29. class procedure AssertEquals(const aMsg: string; aExpected,aActual : TTextNodeKind); overload;
  30. class procedure AssertEquals(const aMsg: string; aExpected,aActual : TNodeStyle); overload;
  31. class procedure AssertEquals(const aMsg: string; aExpected,aActual : TNodeStyles); overload;
  32. protected
  33. procedure TearDown; override;
  34. published
  35. procedure TestSimpleText;
  36. procedure TestBackslashEscapes;
  37. procedure TestCodeSpans;
  38. procedure TestEmphasisAndStrong;
  39. procedure TestEmphasisAndStrongInOne;
  40. procedure TestEmphasisAndStrongInOneSplit;
  41. procedure TestEmphasisAndStrongInOneSplit2;
  42. procedure TestStrikethroughGFM;
  43. procedure TestAutoLinks;
  44. procedure TestInlineLink;
  45. procedure TestInlineImage;
  46. end;
  47. implementation
  48. uses typinfo;
  49. { TTestInlineTextProcessor }
  50. procedure TTestInlineTextProcessor.TearDown;
  51. begin
  52. FProcessor.Free;
  53. FScanner.Free;
  54. FNodes.Free;
  55. FEntities.Free;
  56. end;
  57. procedure TTestInlineTextProcessor.SetupProcessor(const AText: AnsiString; awsMode: TWhitespaceMode = wsTrim);
  58. begin
  59. FScanner := TMarkDownTextScanner.Create(AText, 1);
  60. FNodes := TMarkDownTextNodeList.Create(True);
  61. FEntities := TFPStringHashTable.Create; // Assuming no custom entities for now
  62. FProcessor := TInlineTextProcessor.Create(FScanner, FNodes, FEntities, awsMode);
  63. end;
  64. function TTestInlineTextProcessor.NodeAsText(AIndex: Integer): TMarkDownTextNode;
  65. begin
  66. AssertEquals('Node at index ' + IntToStr(AIndex) + ' should be text', nkText, FNodes[AIndex].Kind);
  67. Result := FNodes[AIndex];
  68. end;
  69. function TTestInlineTextProcessor.NodeAsNamed(AIndex: Integer; AExpectedKind: TTextNodeKind): TMarkDownTextNode;
  70. begin
  71. AssertEquals('Node at index ' + IntToStr(AIndex) + ' should have kind ' + GetEnumName(TypeInfo(TTextNodeKind), Ord(AExpectedKind)), AExpectedKind, FNodes[AIndex].Kind);
  72. Result := FNodes[AIndex];
  73. end;
  74. class procedure TTestInlineTextProcessor.AssertEquals(const aMsg: string; aExpected, aActual: TTextNodeKind);
  75. begin
  76. AssertEquals(aMsg,GetEnumName(TypeInfo(TTextNodeKind),Ord(aExpected)),GetEnumName(TypeInfo(TTextNodeKind),Ord(aActual)));
  77. end;
  78. class procedure TTestInlineTextProcessor.AssertEquals(const aMsg: string; aExpected, aActual: TNodeStyle);
  79. begin
  80. AssertEquals(aMsg,GetEnumName(TypeInfo(TNodeStyle),Ord(aExpected)),GetEnumName(TypeInfo(TNodeStyle),Ord(aActual)));
  81. end;
  82. class procedure TTestInlineTextProcessor.AssertEquals(const aMsg: string; aExpected, aActual: TNodeStyles);
  83. begin
  84. AssertEquals(aMsg,SetToString(PTypeInfo(TypeInfo(TNodeStyles)),Integer(aExpected),False),
  85. SetToString(PTypeInfo(TypeInfo(TNodeStyles)),Integer(aActual),False));
  86. end;
  87. procedure TTestInlineTextProcessor.TestSimpleText;
  88. begin
  89. SetupProcessor('This is a simple text.');
  90. FProcessor.Process(True);
  91. AssertEquals('Should have one text node', 1, FNodes.Count);
  92. AssertEquals('Text content mismatch', 'This is a simple text.', NodeAsText(0).NodeText);
  93. end;
  94. procedure TTestInlineTextProcessor.TestBackslashEscapes;
  95. begin
  96. SetupProcessor('This is \*not\* emphasis.');
  97. FProcessor.Process(True);
  98. AssertEquals('Should have one text node for escaped chars', 1, FNodes.Count);
  99. AssertEquals('Escaped character was not handled correctly', 'This is *not* emphasis.', NodeAsText(0).NodeText);
  100. end;
  101. procedure TTestInlineTextProcessor.DumpNodes;
  102. begin
  103. FProcessor.DumpNodes;
  104. end;
  105. procedure TTestInlineTextProcessor.TestCodeSpans;
  106. var
  107. Node: TMarkDownTextNode;
  108. begin
  109. SetupProcessor('Use the `printf()` function.');
  110. FProcessor.Process(True);
  111. AssertEquals('Should have 3 nodes for a code span', 3, FNodes.Count);
  112. AssertEquals('Text before code span', 'Use the ', NodeAsText(0).NodeText);
  113. Node:=NodeAsNamed(1,nkCode);
  114. AssertEquals('Code span content', 'printf()', Node.NodeText);
  115. AssertEquals('Text after code span', ' function.', NodeAsText(2).NodeText);
  116. end;
  117. procedure TTestInlineTextProcessor.TestEmphasisAndStrong;
  118. var
  119. Node: TMarkDownTextNode;
  120. begin
  121. SetupProcessor('*emphasis* and **strong**');
  122. FProcessor.Process(True);
  123. AssertEquals('Should have 3 nodes for emphasis and strong', 3, FNodes.Count);
  124. // *emphasis*
  125. Node := NodeAsText(0);
  126. AssertEquals('Emphasis content', 'emphasis', Node.NodeText);
  127. AssertEquals('Style', [nsEmph], Node.Styles);
  128. Node:=NodeAsText(1);
  129. AssertEquals('Connector text', ' and ', Node.NodeText);
  130. AssertEquals('Style', [], Node.Styles);
  131. // **strong**
  132. Node := NodeAsText(2);
  133. AssertEquals('Style', [nsStrong], Node.Styles);
  134. AssertEquals('Strong content', 'strong', Node.NodeText);
  135. end;
  136. procedure TTestInlineTextProcessor.TestEmphasisAndStrongInOne;
  137. var
  138. Node: TMarkDownTextNode;
  139. begin
  140. SetupProcessor('***emphasis and strong***');
  141. FProcessor.Process(True);
  142. AssertEquals('Should have 1 node for emphasis and strong', 1, FNodes.Count);
  143. Node := NodeAsText(0);
  144. AssertEquals('Emphasis content', 'emphasis and strong', Node.NodeText);
  145. AssertEquals('Style', [nsStrong,nsEmph], Node.Styles);
  146. end;
  147. procedure TTestInlineTextProcessor.TestEmphasisAndStrongInOneSplit;
  148. var
  149. Node: TMarkDownTextNode;
  150. begin
  151. SetupProcessor('***strong** and emphasis*');
  152. FProcessor.Process(True);
  153. AssertEquals('Should have 2 nodes for emphasis and strong', 2, FNodes.Count);
  154. Node := NodeAsText(0);
  155. AssertEquals('content', 'strong', Node.NodeText);
  156. AssertEquals('Style', [nsStrong,nsEmph], Node.Styles);
  157. Node := NodeAsText(1);
  158. AssertEquals('Emphasis content', ' and emphasis', Node.NodeText);
  159. AssertEquals('Style', [nsEmph], Node.Styles);
  160. end;
  161. procedure TTestInlineTextProcessor.TestEmphasisAndStrongInOneSplit2;
  162. var
  163. Node: TMarkDownTextNode;
  164. begin
  165. SetupProcessor('*emphasis and **strong***');
  166. FProcessor.Process(True);
  167. AssertEquals('Should have 2 nodes for emphasis and strong', 2, FNodes.Count);
  168. Node := NodeAsText(0);
  169. AssertEquals('content', 'emphasis and ', Node.NodeText);
  170. AssertEquals('Style', [nsEmph], Node.Styles);
  171. Node := NodeAsText(1);
  172. AssertEquals('Emphasis content', 'strong', Node.NodeText);
  173. AssertEquals('Style', [nsStrong,nsEmph], Node.Styles);
  174. end;
  175. procedure TTestInlineTextProcessor.TestStrikethroughGFM;
  176. var
  177. Node: TMarkDownTextNode;
  178. begin
  179. SetupProcessor('This is ~~deleted~~ text.');
  180. FProcessor.GFMExtensions := True;
  181. FProcessor.Process(True);
  182. AssertEquals('Should have 3 nodes for strikethrough', 3, FNodes.Count);
  183. AssertEquals('Text before', 'This is ', NodeAsText(0).NodeText);
  184. Node := NodeAsText(1);
  185. AssertEquals('Strikethrough style', [nsDelete], Node.Styles);
  186. AssertEquals('Strikethrough content', 'deleted', Node.NodeText);
  187. Node := NodeAsText(2);
  188. AssertEquals('Text after', ' text.', Node.NodeText);
  189. end;
  190. procedure TTestInlineTextProcessor.TestAutoLinks;
  191. var
  192. Node: TMarkDownTextNode;
  193. begin
  194. SetupProcessor('See <https://www.example.com>.');
  195. FProcessor.Process(True);
  196. AssertEquals('Should have 3 nodes for autolink', 3, FNodes.Count);
  197. AssertEquals('Text before', 'See ', NodeAsText(0).NodeText);
  198. Node := NodeAsNamed(1, nkURI);
  199. AssertEquals('href attribute', 'https://www.example.com', Node.attrs['href']);
  200. AssertEquals('Autolink text content', 'https://www.example.com', Node.NodeText);
  201. AssertEquals('Text after', '.', NodeAsText(2).NodeText);
  202. end;
  203. procedure TTestInlineTextProcessor.TestInlineLink;
  204. var
  205. Node: TMarkDownTextNode;
  206. begin
  207. SetupProcessor('A [link](https://example.com "Title").');
  208. FProcessor.Process(True);
  209. AssertEquals('Should have 3 nodes for inline link', 3, FNodes.Count);
  210. AssertEquals('Text before', 'A ', NodeAsText(0).NodeText);
  211. Node := NodeAsNamed(1, nkURI);
  212. AssertEquals('Link href', 'https://example.com', Node.attrs['href']);
  213. AssertEquals('Link title', 'Title', Node.attrs['title']);
  214. AssertEquals('Link text content', 'link', Node.NodeText);
  215. Node := NodeAsNamed(2, nkText);
  216. AssertEquals('Final text content', '.', Node.NodeText);
  217. end;
  218. procedure TTestInlineTextProcessor.TestInlineImage;
  219. var
  220. Node: TMarkDownTextNode;
  221. begin
  222. SetupProcessor('An image ![alt text](/path/img.jpg "title").');
  223. FProcessor.Process(True);
  224. AssertEquals('Should have 3 nodes for inline image', 3, FNodes.Count);
  225. Node:=NodeAsText(0);
  226. AssertEquals('Text before', 'An image ', Node.NodeText);
  227. Node := NodeAsNamed(1, nkImg);
  228. AssertEquals('Image src', '/path/img.jpg', Node.attrs['src']);
  229. AssertEquals('Image alt', 'alt text', Node.attrs['alt']);
  230. AssertEquals('Image title', 'title', Node.attrs['title']);
  231. Node:=NodeAsText(2);
  232. AssertEquals('Text after', '.', Node.NodeText);
  233. end;
  234. initialization
  235. RegisterTest(TTestInlineTextProcessor);
  236. end.