unittest.htmlrender.pp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2025 by Michael Van Canneyt
  4. HTML renderer unit test
  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 unittest.htmlrender;
  12. interface
  13. {$mode objfpc}{$H+}
  14. uses
  15. Classes, SysUtils, fpcunit, testregistry,
  16. syntax.highlighter, syntax.javascript, syntax.css, syntax.html,
  17. syntax.htmlrender;
  18. type
  19. TTestHtmlRenderer = class(TTestCase)
  20. protected
  21. procedure SetUp; override;
  22. procedure TearDown; override;
  23. private
  24. renderer: THtmlSyntaxRenderer;
  25. function RenderToString(const tokens: TSyntaxTokenArray): string;
  26. function RenderToStringList(const tokens: TSyntaxTokenArray): TStringList;
  27. published
  28. procedure TestJavaScriptRendering;
  29. procedure TestCssRendering;
  30. procedure TestHtmlRendering;
  31. procedure TestEmbeddedHtmlRendering;
  32. procedure TestStringOutput;
  33. procedure TestSpecialCharacterEscaping;
  34. procedure TestEmptyTokenArray;
  35. procedure TestClassNameGeneration;
  36. procedure TestCategoryMapping;
  37. procedure TestHtmlEscaping;
  38. procedure TestTokensWithoutCategory;
  39. procedure TestComplexJavaScript;
  40. procedure TestNoDefaultSpanOption;
  41. procedure TestMultilineRendering;
  42. procedure TestPreserveLineStructureOption;
  43. procedure TestExtraClassesProperty;
  44. end;
  45. implementation
  46. procedure TTestHtmlRenderer.SetUp;
  47. begin
  48. renderer := THtmlSyntaxRenderer.Create;
  49. end;
  50. procedure TTestHtmlRenderer.TearDown;
  51. begin
  52. renderer.Free;
  53. end;
  54. function TTestHtmlRenderer.RenderToString(const tokens: TSyntaxTokenArray): string;
  55. begin
  56. Result:='';
  57. renderer.RenderTokensToString(tokens, Result);
  58. end;
  59. function TTestHtmlRenderer.RenderToStringList(const tokens: TSyntaxTokenArray): TStringList;
  60. begin
  61. Result := TStringList.Create;
  62. renderer.RenderTokens(tokens, Result);
  63. end;
  64. procedure TTestHtmlRenderer.TestJavaScriptRendering;
  65. var
  66. jsHighlighter: TJavaScriptSyntaxHighlighter;
  67. tokens: TSyntaxTokenArray;
  68. output: string;
  69. begin
  70. jsHighlighter := TJavaScriptSyntaxHighlighter.Create;
  71. try
  72. tokens := jsHighlighter.Execute( 'function test() { return "hello"; }');
  73. output := RenderToString(tokens);
  74. // Check for essential elements
  75. AssertTrue('Should contain function keyword', Pos('<span class="keyword keyword-javascript">function</span>', output) > 0);
  76. AssertTrue('Should contain string literal', Pos('<span class="strings strings-javascript">&quot;hello&quot;</span>', output) > 0);
  77. AssertTrue('Should contain return keyword', Pos('<span class="keyword keyword-javascript">return</span>', output) > 0);
  78. AssertTrue('Should contain symbols', Pos('<span class="symbol symbol-javascript">', output) > 0);
  79. finally
  80. jsHighlighter.Free;
  81. end;
  82. end;
  83. procedure TTestHtmlRenderer.TestCssRendering;
  84. var
  85. cssHighlighter: TCssSyntaxHighlighter;
  86. tokens: TSyntaxTokenArray;
  87. output: string;
  88. begin
  89. cssHighlighter := TCssSyntaxHighlighter.Create;
  90. try
  91. tokens := cssHighlighter.Execute( 'body { color: #FF0000; font-size: 16px; }');
  92. output := RenderToString(tokens);
  93. // Check for essential CSS elements
  94. AssertTrue('Should contain color property', Pos('<span class="keyword keyword-css">color</span>', output) > 0);
  95. AssertTrue('Should contain hex color', Pos('<span class="numbers numbers-css">#FF0000</span>', output) > 0);
  96. AssertTrue('Should contain pixel value', Pos('<span class="numbers numbers-css">16px</span>', output) > 0);
  97. AssertTrue('Should contain CSS symbols', Pos('<span class="symbol symbol-css">{</span>', output) > 0);
  98. finally
  99. cssHighlighter.Free;
  100. end;
  101. end;
  102. procedure TTestHtmlRenderer.TestHtmlRendering;
  103. var
  104. htmlHighlighter: THtmlSyntaxHighlighter;
  105. tokens: TSyntaxTokenArray;
  106. output: string;
  107. begin
  108. htmlHighlighter := THtmlSyntaxHighlighter.Create;
  109. try
  110. tokens := htmlHighlighter.Execute( '<div class="container">&lt;Hello&gt;</div>');
  111. output := RenderToString(tokens);
  112. // Check for essential HTML elements
  113. AssertTrue('Should contain div keyword', Pos('<span class="keyword keyword-html">div</span>', output) > 0);
  114. AssertTrue('Should contain class attribute', Pos('<span class="default default-html default-htmlattribute">class</span>', output) > 0);
  115. AssertTrue('Should contain string value', Pos('<span class="strings strings-html">&quot;container&quot;</span>', output) > 0);
  116. AssertTrue('Should contain HTML symbols', Pos('<span class="symbol symbol-html">&lt;</span>', output) > 0);
  117. AssertTrue('Should contain escaped entities', Pos('<span class="escape escape-html">&amp;lt;</span>', output) > 0);
  118. finally
  119. htmlHighlighter.Free;
  120. end;
  121. end;
  122. procedure TTestHtmlRenderer.TestEmbeddedHtmlRendering;
  123. var
  124. htmlHighlighter: THtmlSyntaxHighlighter;
  125. tokens: TSyntaxTokenArray;
  126. output: string;
  127. begin
  128. htmlHighlighter := THtmlSyntaxHighlighter.Create;
  129. try
  130. tokens := htmlHighlighter.Execute( '<style>body { color: red; }</style><script>alert("test");</script>');
  131. output := RenderToString(tokens);
  132. // Check for embedded CSS
  133. // Writeln('output ',output);
  134. AssertTrue('Should contain CSS color property', Pos('<span class="keyword keyword-embeddedcss">color</span>', output) > 0);
  135. AssertTrue('Should contain CSS category', Pos('embeddedcss', output) > 0);
  136. // Check for embedded JavaScript
  137. AssertTrue('Should contain JS alert function', Pos('<span class="default default-embeddedjs">alert</span>', output) > 0);
  138. AssertTrue('Should contain JS category', Pos('embeddedjs', output) > 0);
  139. // Check for HTML tags
  140. AssertTrue('Should contain style tag', Pos('<span class="keyword keyword-html">style</span>', output) > 0);
  141. AssertTrue('Should contain script tag', Pos('<span class="keyword keyword-html">script</span>', output) > 0);
  142. finally
  143. htmlHighlighter.Free;
  144. end;
  145. end;
  146. procedure TTestHtmlRenderer.TestStringOutput;
  147. var
  148. jsHighlighter: TJavaScriptSyntaxHighlighter;
  149. tokens: TSyntaxTokenArray;
  150. output: string;
  151. begin
  152. jsHighlighter := TJavaScriptSyntaxHighlighter.Create;
  153. try
  154. tokens := jsHighlighter.Execute( 'var x = 42;');
  155. output := RenderToString(tokens);
  156. // Should be single continuous string without line breaks
  157. AssertTrue('Should not contain line breaks', Pos(#10, output) = 0);
  158. AssertTrue('Should not contain line breaks', Pos(#13, output) = 0);
  159. // Should contain all expected elements in sequence
  160. AssertTrue('Should contain var keyword', Pos('<span class="keyword keyword-javascript">var</span>', output) > 0);
  161. AssertTrue('Should contain number', Pos('<span class="numbers numbers-javascript">42</span>', output) > 0);
  162. AssertTrue('Should contain operator', Pos('<span class="operator operator-javascript">=</span>', output) > 0);
  163. finally
  164. jsHighlighter.Free;
  165. end;
  166. end;
  167. procedure TTestHtmlRenderer.TestSpecialCharacterEscaping;
  168. var
  169. jsHighlighter: TJavaScriptSyntaxHighlighter;
  170. tokens: TSyntaxTokenArray;
  171. output: string;
  172. begin
  173. jsHighlighter := TJavaScriptSyntaxHighlighter.Create;
  174. try
  175. tokens := jsHighlighter.Execute( 'var html = "<div>\"Hello & Welcome\"</div>";');
  176. output := RenderToString(tokens);
  177. // Check that special characters are properly escaped
  178. AssertTrue('Should escape < character', Pos('&lt;', output) > 0);
  179. AssertTrue('Should escape > character', Pos('&gt;', output) > 0);
  180. AssertTrue('Should escape & character', Pos('&amp;', output) > 0);
  181. AssertTrue('Should escape " character', Pos('&quot;', output) > 0);
  182. // Should not contain unescaped special characters in content
  183. AssertFalse('Should not contain raw < in content', Pos('>"<', output) > 0);
  184. AssertFalse('Should not contain raw > in content', Pos('>>', output) > 0);
  185. finally
  186. jsHighlighter.Free;
  187. end;
  188. end;
  189. procedure TTestHtmlRenderer.TestEmptyTokenArray;
  190. var
  191. tokens: TSyntaxTokenArray;
  192. output: string;
  193. stringList: TStringList;
  194. begin
  195. tokens:=[];
  196. SetLength(tokens, 0);
  197. // Test string output
  198. output := RenderToString(tokens);
  199. AssertEquals('Empty token array should produce empty string', '', output);
  200. // Test string list output
  201. stringList := RenderToStringList(tokens);
  202. try
  203. AssertEquals('Empty token array should produce empty string list', 0, stringList.Count);
  204. finally
  205. stringList.Free;
  206. end;
  207. end;
  208. procedure TTestHtmlRenderer.TestClassNameGeneration;
  209. var
  210. jsHighlighter: TJavaScriptSyntaxHighlighter;
  211. tokens: TSyntaxTokenArray;
  212. output: string;
  213. begin
  214. jsHighlighter := TJavaScriptSyntaxHighlighter.Create;
  215. try
  216. tokens := jsHighlighter.Execute( 'var');
  217. output := RenderToString(tokens);
  218. // Should have both base class and category-specific class
  219. AssertTrue('Should contain base keyword class', Pos('class="keyword keyword-javascript"', output) > 0);
  220. finally
  221. jsHighlighter.Free;
  222. end;
  223. end;
  224. procedure TTestHtmlRenderer.TestCategoryMapping;
  225. var
  226. cssHighlighter: TCssSyntaxHighlighter;
  227. tokens: TSyntaxTokenArray;
  228. output: string;
  229. begin
  230. cssHighlighter := TCssSyntaxHighlighter.Create;
  231. try
  232. tokens := cssHighlighter.Execute( 'color');
  233. output := RenderToString(tokens);
  234. // Should map CSS category correctly
  235. AssertTrue('Should contain CSS category', Pos('keyword-css', output) > 0);
  236. finally
  237. cssHighlighter.Free;
  238. end;
  239. end;
  240. procedure TTestHtmlRenderer.TestHtmlEscaping;
  241. var
  242. tokens: TSyntaxTokenArray;
  243. output: string;
  244. begin
  245. // Create a simple token with special characters
  246. tokens:=[];
  247. SetLength(tokens, 1);
  248. tokens[0] := TSyntaxToken.Create('<script>alert("test");</script>',shDefault);
  249. output := RenderToString(tokens);
  250. // All HTML special characters should be escaped
  251. AssertTrue('Should escape <script>', Pos('&lt;script&gt;', output) > 0);
  252. AssertTrue('Should escape quotes', Pos('&quot;test&quot;', output) > 0);
  253. AssertTrue('Should escape closing tag', Pos('&lt;/script&gt;', output) > 0);
  254. end;
  255. procedure TTestHtmlRenderer.TestTokensWithoutCategory;
  256. var
  257. tokens: TSyntaxTokenArray;
  258. output: string;
  259. begin
  260. tokens:=[];
  261. // Create tokens without category
  262. SetLength(tokens, 2);
  263. tokens[0] := TSyntaxToken.Create('hello',shKeyword);
  264. tokens[1] := TSyntaxToken.Create('world',shDefault);
  265. output := RenderToString(tokens);
  266. // Should have basic class names without category suffix
  267. AssertTrue('Should contain keyword class without category', Pos('<span class="keyword">hello</span>', output) > 0);
  268. AssertTrue('Should contain default class without category', Pos('<span class="default">world</span>', output) > 0);
  269. end;
  270. procedure TTestHtmlRenderer.TestComplexJavaScript;
  271. var
  272. jsHighlighter: TJavaScriptSyntaxHighlighter;
  273. tokens: TSyntaxTokenArray;
  274. stringList: TStringList;
  275. output, fullOutput: string;
  276. begin
  277. jsHighlighter := TJavaScriptSyntaxHighlighter.Create;
  278. try
  279. tokens := jsHighlighter.Execute( 'function add(a, b) { return a + b; }');
  280. // Test string list output
  281. stringList := RenderToStringList(tokens);
  282. try
  283. AssertEquals('Should have exactly one output line', 1, stringList.Count);
  284. // Get the single line of output
  285. fullOutput := stringList[0];
  286. AssertTrue('Should contain function keyword', Pos('keyword keyword-javascript">function', fullOutput) > 0);
  287. AssertTrue('Should contain return keyword', Pos('keyword keyword-javascript">return', fullOutput) > 0);
  288. AssertTrue('Should contain operator', Pos('operator operator-javascript">+', fullOutput) > 0);
  289. finally
  290. stringList.Free;
  291. end;
  292. // Compare with single string output
  293. output := RenderToString(tokens);
  294. AssertEquals('String and concatenated string list should be equal', fullOutput, output);
  295. finally
  296. jsHighlighter.Free;
  297. end;
  298. end;
  299. procedure TTestHtmlRenderer.TestNoDefaultSpanOption;
  300. var
  301. jsHighlighter: TJavaScriptSyntaxHighlighter;
  302. tokens: TSyntaxTokenArray;
  303. outputDefault, outputNoSpan: string;
  304. begin
  305. jsHighlighter := TJavaScriptSyntaxHighlighter.Create;
  306. try
  307. tokens := jsHighlighter.Execute( 'var x = 42;');
  308. // Test default behavior (with default spans)
  309. renderer.Options := [];
  310. outputDefault := RenderToString(tokens);
  311. // Test with hroNoDefaultSpan option
  312. renderer.Options := [hroNoDefaultSpan];
  313. outputNoSpan := RenderToString(tokens);
  314. // Default behavior should contain default spans
  315. AssertTrue('Default output should contain default spans',
  316. Pos('<span class="default default-javascript">', outputDefault) > 0);
  317. // With hroNoDefaultSpan, default tokens should not be wrapped in spans
  318. AssertFalse('NoDefaultSpan output should not contain default spans',
  319. Pos('<span class="default default-javascript">', outputNoSpan) > 0);
  320. // Both outputs should still contain non-default spans
  321. AssertTrue('Default output should contain keyword spans',
  322. Pos('<span class="keyword keyword-javascript">var</span>', outputDefault) > 0);
  323. AssertTrue('NoDefaultSpan output should contain keyword spans',
  324. Pos('<span class="keyword keyword-javascript">var</span>', outputNoSpan) > 0);
  325. // Both outputs should still contain number spans
  326. AssertTrue('Default output should contain number spans',
  327. Pos('<span class="numbers numbers-javascript">42</span>', outputDefault) > 0);
  328. AssertTrue('NoDefaultSpan output should contain number spans',
  329. Pos('<span class="numbers numbers-javascript">42</span>', outputNoSpan) > 0);
  330. // NoDefaultSpan output should contain raw whitespace and identifier text
  331. AssertTrue('NoDefaultSpan output should contain raw whitespace', Pos(' x ', outputNoSpan) > 0);
  332. // Verify that default tokens are still HTML-escaped even when not wrapped in spans
  333. renderer.Options := [hroNoDefaultSpan];
  334. tokens := jsHighlighter.Execute( 'var html = "<test>";');
  335. outputNoSpan := RenderToString(tokens);
  336. // Should contain escaped < and > characters even in unwrapped default tokens
  337. AssertTrue('Should escape < character in unwrapped default tokens', Pos('&lt;', outputNoSpan) > 0);
  338. AssertTrue('Should escape > character in unwrapped default tokens', Pos('&gt;', outputNoSpan) > 0);
  339. finally
  340. jsHighlighter.Free;
  341. // Reset options to default for other tests
  342. renderer.Options := [];
  343. end;
  344. end;
  345. procedure TTestHtmlRenderer.TestMultilineRendering;
  346. var
  347. jsHighlighter: TJavaScriptSyntaxHighlighter;
  348. tokens: TSyntaxTokenArray;
  349. output: string;
  350. multilineCode: string;
  351. begin
  352. jsHighlighter := TJavaScriptSyntaxHighlighter.Create;
  353. try
  354. // Test multiline JavaScript code
  355. multilineCode := 'function test() {' + #10 + ' return "hello";' + #10 + '}';
  356. tokens := jsHighlighter.Execute( multilineCode);
  357. // Default behavior should preserve newlines in single line output
  358. renderer.Options := [];
  359. output := RenderToString(tokens);
  360. // Should contain the newlines from original code
  361. AssertTrue('Should contain newline characters', Pos(#10, output) > 0);
  362. AssertTrue('Should contain function keyword', Pos('<span class="keyword keyword-javascript">function</span>', output) > 0);
  363. AssertTrue('Should contain return keyword', Pos('<span class="keyword keyword-javascript">return</span>', output) > 0);
  364. AssertTrue('Should contain indentation spaces', Pos(' ', output) > 0);
  365. finally
  366. jsHighlighter.Free;
  367. end;
  368. end;
  369. procedure TTestHtmlRenderer.TestPreserveLineStructureOption;
  370. var
  371. jsHighlighter: TJavaScriptSyntaxHighlighter;
  372. tokens: TSyntaxTokenArray;
  373. stringListDefault, stringListPreserved: TStringList;
  374. outputDefault, outputPreserved: string;
  375. multilineCode: string;
  376. i: Integer;
  377. begin
  378. jsHighlighter := TJavaScriptSyntaxHighlighter.Create;
  379. try
  380. multilineCode := 'function test() {' + #10 + ' return "hello";' + #10 + '}';
  381. tokens := jsHighlighter.Execute( multilineCode);
  382. // Test default behavior (single line)
  383. renderer.Options := [];
  384. stringListDefault := RenderToStringList(tokens);
  385. try
  386. AssertEquals('Default should have 1 line', 1, stringListDefault.Count);
  387. outputDefault := stringListDefault[0];
  388. AssertTrue('Default output should contain newlines', Pos(#10, outputDefault) > 0);
  389. finally
  390. stringListDefault.Free;
  391. end;
  392. // Test with hroPreserveLineStructure (multiple lines)
  393. renderer.Options := [hroPreserveLineStructure];
  394. stringListPreserved := RenderToStringList(tokens);
  395. try
  396. AssertTrue('Preserved should have multiple lines', stringListPreserved.Count > 1);
  397. AssertEquals('Should have exactly 3 lines', 3, stringListPreserved.Count);
  398. // First line should contain function declaration
  399. AssertTrue('First line should contain function',
  400. Pos('<span class="keyword keyword-javascript">function</span>', stringListPreserved[0]) > 0);
  401. // Second line should contain return statement with indentation
  402. AssertTrue('Second line should contain return',
  403. Pos('<span class="keyword keyword-javascript">return</span>', stringListPreserved[1]) > 0);
  404. AssertTrue('Second line should contain indentation',
  405. Pos(' ', stringListPreserved[1]) > 0);
  406. // Third line should contain closing brace
  407. AssertTrue('Third line should contain closing brace',
  408. Pos('<span class="symbol symbol-javascript">}</span>', stringListPreserved[2]) > 0);
  409. // When concatenated, should be equivalent to single-line version
  410. outputPreserved := '';
  411. for i := 0 to stringListPreserved.Count - 1 do
  412. begin
  413. if i > 0 then
  414. outputPreserved := outputPreserved + #10;
  415. outputPreserved := outputPreserved + stringListPreserved[i];
  416. end;
  417. AssertEquals('Concatenated preserved output should equal default output',
  418. outputDefault, outputPreserved);
  419. finally
  420. stringListPreserved.Free;
  421. end;
  422. // Test combination with hroNoDefaultSpan
  423. renderer.Options := [hroPreserveLineStructure, hroNoDefaultSpan];
  424. stringListPreserved := RenderToStringList(tokens);
  425. try
  426. AssertTrue('Combined options should have multiple lines', stringListPreserved.Count > 1);
  427. // Should not contain default spans
  428. AssertFalse('Should not contain default spans with combined options',
  429. Pos('<span class="default default-javascript">', stringListPreserved.Text) > 0);
  430. // Should still contain keyword spans
  431. AssertTrue('Should still contain keyword spans with combined options',
  432. Pos('<span class="keyword keyword-javascript">function</span>', stringListPreserved.Text) > 0);
  433. finally
  434. stringListPreserved.Free;
  435. end;
  436. finally
  437. jsHighlighter.Free;
  438. // Reset options to default for other tests
  439. renderer.Options := [];
  440. end;
  441. end;
  442. procedure TTestHtmlRenderer.TestExtraClassesProperty;
  443. var
  444. jsHighlighter: TJavaScriptSyntaxHighlighter;
  445. tokens: TSyntaxTokenArray;
  446. outputDefault, outputWithExtra: string;
  447. begin
  448. jsHighlighter := TJavaScriptSyntaxHighlighter.Create;
  449. try
  450. tokens := jsHighlighter.Execute( 'var x = 42;');
  451. // Test default behavior (no extra classes)
  452. renderer.ExtraClasses := '';
  453. outputDefault := RenderToString(tokens);
  454. // Test with single extra class
  455. renderer.ExtraClasses := 'my-extra-class';
  456. outputWithExtra := RenderToString(tokens);
  457. // Default output should not contain extra class
  458. AssertFalse('Default output should not contain extra class',
  459. Pos('my-extra-class', outputDefault) > 0);
  460. // Output with extra classes should contain the extra class
  461. AssertTrue('Output with extra classes should contain my-extra-class',
  462. Pos('my-extra-class', outputWithExtra) > 0);
  463. // Should contain extra class in keyword spans
  464. AssertTrue('Should contain extra class in keyword spans',
  465. Pos('<span class="keyword keyword-javascript my-extra-class">var</span>', outputWithExtra) > 0);
  466. // Should contain extra class in number spans
  467. AssertTrue('Should contain extra class in number spans',
  468. Pos('<span class="numbers numbers-javascript my-extra-class">42</span>', outputWithExtra) > 0);
  469. // Should contain extra class in operator spans
  470. AssertTrue('Should contain extra class in operator spans',
  471. Pos('<span class="operator operator-javascript my-extra-class">=</span>', outputWithExtra) > 0);
  472. // Test with multiple extra classes
  473. renderer.ExtraClasses := 'class1 class2 class3';
  474. outputWithExtra := RenderToString(tokens);
  475. // Should contain all extra classes
  476. AssertTrue('Should contain all extra classes',
  477. Pos('class1 class2 class3', outputWithExtra) > 0);
  478. AssertTrue('Should contain multiple extra classes in keyword spans',
  479. Pos('<span class="keyword keyword-javascript class1 class2 class3">var</span>', outputWithExtra) > 0);
  480. // Test empty extra classes (should behave like default)
  481. renderer.ExtraClasses := '';
  482. outputWithExtra := RenderToString(tokens);
  483. AssertEquals('Empty extra classes should equal default output',
  484. outputDefault, outputWithExtra);
  485. // Test extra classes with hroNoDefaultSpan option
  486. renderer.Options := [hroNoDefaultSpan];
  487. renderer.ExtraClasses := 'no-default-extra';
  488. outputWithExtra := RenderToString(tokens);
  489. // Should not contain extra classes for default tokens (they're not wrapped)
  490. AssertFalse('Should not contain extra classes for unwrapped default tokens',
  491. Pos('<span class="default default-javascript no-default-extra">', outputWithExtra) > 0);
  492. // But should contain extra classes for non-default tokens
  493. AssertTrue('Should contain extra classes in non-default spans with hroNoDefaultSpan',
  494. Pos('<span class="keyword keyword-javascript no-default-extra">var</span>', outputWithExtra) > 0);
  495. finally
  496. jsHighlighter.Free;
  497. // Reset properties to default for other tests
  498. renderer.Options := [];
  499. renderer.ExtraClasses := '';
  500. end;
  501. end;
  502. initialization
  503. RegisterTest(TTestHtmlRenderer);
  504. end.