unittest.css.pp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2025 by Michael Van Canneyt
  4. CSS highlighter 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.css;
  12. interface
  13. {$mode objfpc}{$H+}
  14. uses
  15. Classes, SysUtils, fpcunit, testregistry,
  16. syntax.highlighter, syntax.css;
  17. type
  18. TTestCssHighlighter = class(TTestCase)
  19. protected
  20. procedure SetUp; override;
  21. procedure TearDown; override;
  22. private
  23. function DoCssHighlighting(const source: string): TSyntaxTokenArray;
  24. published
  25. procedure TestCssAtRules;
  26. procedure TestCssProperties;
  27. procedure TestCssStrings;
  28. procedure TestCssNumbers;
  29. procedure TestCssColors;
  30. procedure TestCssComments;
  31. procedure TestCssSelectors;
  32. procedure TestCssSymbols;
  33. procedure TestCssUrls;
  34. procedure TestComplexCssRule;
  35. procedure TestCssMediaQuery;
  36. procedure TestCssUnits;
  37. end;
  38. implementation
  39. procedure TTestCssHighlighter.SetUp;
  40. begin
  41. end;
  42. procedure TTestCssHighlighter.TearDown;
  43. begin
  44. // Nothing to do
  45. end;
  46. function TTestCssHighlighter.DoCssHighlighting(const source: string): TSyntaxTokenArray;
  47. var
  48. highlighter: TCssSyntaxHighlighter;
  49. begin
  50. highlighter := TCssSyntaxHighlighter.Create;
  51. try
  52. Result := highlighter.Execute(source);
  53. finally
  54. highlighter.Free;
  55. end;
  56. end;
  57. procedure TTestCssHighlighter.TestCssAtRules;
  58. const
  59. AtRules: array[0..9] of string = (
  60. '@charset', '@import', '@media', '@keyframes', '@font-face',
  61. '@supports', '@page', '@namespace', '@viewport', '@layer'
  62. );
  63. var
  64. tokens: TSyntaxTokenArray;
  65. i: Integer;
  66. begin
  67. for i := 0 to High(AtRules) do
  68. begin
  69. tokens := DoCssHighlighting(AtRules[i]);
  70. AssertEquals('Should have 1 token for ' + AtRules[i], 1, Length(tokens));
  71. AssertEquals('Token should be ' + AtRules[i], AtRules[i], tokens[0].Text);
  72. AssertEquals(AtRules[i] + ' should be directive', Ord(shDirective), Ord(tokens[0].Kind));
  73. end;
  74. end;
  75. procedure TTestCssHighlighter.TestCssProperties;
  76. const
  77. Properties: array[0..9] of string = (
  78. 'color', 'background', 'margin', 'padding', 'border',
  79. 'font', 'width', 'height', 'position', 'display'
  80. );
  81. var
  82. tokens: TSyntaxTokenArray;
  83. i: Integer;
  84. begin
  85. for i := 0 to High(Properties) do
  86. begin
  87. tokens := DoCssHighlighting(Properties[i]);
  88. AssertEquals('Should have 1 token for ' + Properties[i], 1, Length(tokens));
  89. AssertEquals('Token should be ' + Properties[i], Properties[i], tokens[0].Text);
  90. AssertEquals(Properties[i] + ' should be keyword (property)', Ord(shKeyword), Ord(tokens[0].Kind));
  91. end;
  92. end;
  93. procedure TTestCssHighlighter.TestCssStrings;
  94. var
  95. tokens: TSyntaxTokenArray;
  96. begin
  97. // Test single-quoted string
  98. tokens := DoCssHighlighting('''Arial''');
  99. AssertEquals('Should have 1 token', 1, Length(tokens));
  100. AssertEquals('Token should be single-quoted string', '''Arial''', tokens[0].Text);
  101. AssertEquals('Token should be string', Ord(shStrings), Ord(tokens[0].Kind));
  102. // Test double-quoted string
  103. tokens := DoCssHighlighting('"Helvetica"');
  104. AssertEquals('Should have 1 token', 1, Length(tokens));
  105. AssertEquals('Token should be double-quoted string', '"Helvetica"', tokens[0].Text);
  106. AssertEquals('Token should be string', Ord(shStrings), Ord(tokens[0].Kind));
  107. // Test string with escapes
  108. tokens := DoCssHighlighting('"Font with \"quotes\""');
  109. AssertEquals('Should have 1 token', 1, Length(tokens));
  110. AssertEquals('Token should be escaped string', '"Font with \"quotes\""', tokens[0].Text);
  111. AssertEquals('Token should be string', Ord(shStrings), Ord(tokens[0].Kind));
  112. end;
  113. procedure TTestCssHighlighter.TestCssNumbers;
  114. var
  115. tokens: TSyntaxTokenArray;
  116. begin
  117. // Test percentage
  118. tokens := DoCssHighlighting('100%');
  119. AssertEquals('Should have 1 token', 1, Length(tokens));
  120. AssertEquals('Token should be percentage', '100%', tokens[0].Text);
  121. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  122. // Test number with px unit
  123. tokens := DoCssHighlighting('16px');
  124. AssertEquals('Should have 1 token', 1, Length(tokens));
  125. AssertEquals('Token should be pixel value', '16px', tokens[0].Text);
  126. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  127. // Test decimal number
  128. tokens := DoCssHighlighting('1.5em');
  129. AssertEquals('Should have 1 token', 1, Length(tokens));
  130. AssertEquals('Token should be em value', '1.5em', tokens[0].Text);
  131. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  132. // Test zero value
  133. tokens := DoCssHighlighting('0');
  134. AssertEquals('Should have 1 token', 1, Length(tokens));
  135. AssertEquals('Token should be zero', '0', tokens[0].Text);
  136. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  137. end;
  138. procedure TTestCssHighlighter.TestCssColors;
  139. var
  140. tokens: TSyntaxTokenArray;
  141. begin
  142. // Test 6-digit hex color
  143. tokens := DoCssHighlighting('#FF0000');
  144. AssertEquals('Should have 1 token', 1, Length(tokens));
  145. AssertEquals('Token should be hex color', '#FF0000', tokens[0].Text);
  146. AssertEquals('Token should be number (color)', Ord(shNumbers), Ord(tokens[0].Kind));
  147. // Test 3-digit hex color
  148. tokens := DoCssHighlighting('#F00');
  149. AssertEquals('Should have 1 token', 1, Length(tokens));
  150. AssertEquals('Token should be 3-digit hex color', '#F00', tokens[0].Text);
  151. AssertEquals('Token should be number (color)', Ord(shNumbers), Ord(tokens[0].Kind));
  152. // Test lowercase hex color
  153. tokens := DoCssHighlighting('#ff0000');
  154. AssertEquals('Should have 1 token', 1, Length(tokens));
  155. AssertEquals('Token should be lowercase hex color', '#ff0000', tokens[0].Text);
  156. AssertEquals('Token should be number (color)', Ord(shNumbers), Ord(tokens[0].Kind));
  157. end;
  158. procedure TTestCssHighlighter.TestCssComments;
  159. var
  160. tokens: TSyntaxTokenArray;
  161. begin
  162. // Test multi-line comment
  163. tokens := DoCssHighlighting('/* This is a comment */');
  164. AssertEquals('Should have 1 token', 1, Length(tokens));
  165. AssertEquals('Token should be multi-line comment', '/* This is a comment */', tokens[0].Text);
  166. AssertEquals('Token should be comment', Ord(shComment), Ord(tokens[0].Kind));
  167. // Test multi-line comment with newlines
  168. tokens := DoCssHighlighting('/* Line 1' + #10 + 'Line 2 */');
  169. AssertEquals('Should have 1 token', 1, Length(tokens));
  170. AssertEquals('Token should be multi-line comment with newlines', '/* Line 1' + #10 + 'Line 2 */', tokens[0].Text);
  171. AssertEquals('Token should be comment', Ord(shComment), Ord(tokens[0].Kind));
  172. // Test comment with CSS inside
  173. tokens := DoCssHighlighting('/* color: red; */');
  174. AssertEquals('Should have 1 token', 1, Length(tokens));
  175. AssertEquals('Token should be comment with CSS', '/* color: red; */', tokens[0].Text);
  176. AssertEquals('Token should be comment', Ord(shComment), Ord(tokens[0].Kind));
  177. end;
  178. procedure TTestCssHighlighter.TestCssSelectors;
  179. var
  180. tokens: TSyntaxTokenArray;
  181. begin
  182. // Test class selector
  183. tokens := DoCssHighlighting('.myClass');
  184. AssertEquals('Should have 1 token', 1, Length(tokens));
  185. AssertEquals('Token should be class selector', '.myClass', tokens[0].Text);
  186. AssertEquals('Token should be default (selector)', Ord(shDefault), Ord(tokens[0].Kind));
  187. // Test element selector
  188. tokens := DoCssHighlighting('div');
  189. AssertEquals('Should have 1 token', 1, Length(tokens));
  190. AssertEquals('Token should be element selector', 'div', tokens[0].Text);
  191. AssertEquals('Token should be default (selector)', Ord(shDefault), Ord(tokens[0].Kind));
  192. // Test pseudo-class
  193. tokens := DoCssHighlighting(':hover');
  194. AssertTrue('Should have at least 1 token', Length(tokens) >= 1);
  195. // First part should be the colon or the complete pseudo-class
  196. end;
  197. procedure TTestCssHighlighter.TestCssSymbols;
  198. var
  199. tokens: TSyntaxTokenArray;
  200. begin
  201. // Test opening brace
  202. tokens := DoCssHighlighting('{');
  203. AssertEquals('Should have 1 token', 1, Length(tokens));
  204. AssertEquals('Token should be opening brace', '{', tokens[0].Text);
  205. AssertEquals('Token should be symbol', Ord(shSymbol), Ord(tokens[0].Kind));
  206. // Test closing brace
  207. tokens := DoCssHighlighting('}');
  208. AssertEquals('Should have 1 token', 1, Length(tokens));
  209. AssertEquals('Token should be closing brace', '}', tokens[0].Text);
  210. AssertEquals('Token should be symbol', Ord(shSymbol), Ord(tokens[0].Kind));
  211. // Test semicolon
  212. tokens := DoCssHighlighting(';');
  213. AssertEquals('Should have 1 token', 1, Length(tokens));
  214. AssertEquals('Token should be semicolon', ';', tokens[0].Text);
  215. AssertEquals('Token should be symbol', Ord(shSymbol), Ord(tokens[0].Kind));
  216. // Test colon
  217. tokens := DoCssHighlighting(':');
  218. AssertEquals('Should have 1 token', 1, Length(tokens));
  219. AssertEquals('Token should be colon', ':', tokens[0].Text);
  220. AssertEquals('Token should be default', Ord(shDefault), Ord(tokens[0].Kind));
  221. end;
  222. procedure TTestCssHighlighter.TestCssUrls;
  223. var
  224. tokens: TSyntaxTokenArray;
  225. begin
  226. // Test URL function
  227. tokens := DoCssHighlighting('url(image.png)');
  228. AssertEquals('Should have 1 token', 1, Length(tokens));
  229. AssertEquals('Token should be URL function', 'url(image.png)', tokens[0].Text);
  230. AssertEquals('Token should be string (URL)', Ord(shStrings), Ord(tokens[0].Kind));
  231. // Test URL with quotes
  232. tokens := DoCssHighlighting('url("image.png")');
  233. AssertTrue('Should have at least 1 token', Length(tokens) >= 1);
  234. // Should be tokenized as URL function
  235. end;
  236. procedure TTestCssHighlighter.TestComplexCssRule;
  237. var
  238. tokens: TSyntaxTokenArray;
  239. cssRule: string;
  240. i: Integer;
  241. hasSelectors, hasProperties, hasSymbols, hasValues: Boolean;
  242. begin
  243. cssRule := '.container { width: 100%; color: #333; }';
  244. tokens := DoCssHighlighting(cssRule);
  245. AssertTrue('Should have multiple tokens', Length(tokens) > 5);
  246. // Check that we have different token types
  247. hasSelectors := False;
  248. hasProperties := False;
  249. hasSymbols := False;
  250. hasValues := False;
  251. for i := 0 to High(tokens) do
  252. begin
  253. case tokens[i].Kind of
  254. shDefault: hasSelectors := True;
  255. shKeyword: hasProperties := True;
  256. shSymbol: hasSymbols := True;
  257. shNumbers: hasValues := True;
  258. end;
  259. end;
  260. AssertTrue('Should contain selector tokens', hasSelectors);
  261. AssertTrue('Should contain property tokens', hasProperties);
  262. AssertTrue('Should contain symbol tokens', hasSymbols);
  263. AssertTrue('Should contain value tokens', hasValues);
  264. // First token should be the selector
  265. AssertEquals('First token should be .container', '.container', tokens[0].Text);
  266. AssertEquals('First token should be default (selector)', Ord(shDefault), Ord(tokens[0].Kind));
  267. // Should contain braces
  268. for i := 0 to High(tokens) do
  269. begin
  270. if tokens[i].Text = '{' then
  271. begin
  272. AssertEquals('Opening brace should be symbol', Ord(shSymbol), Ord(tokens[i].Kind));
  273. Break;
  274. end;
  275. end;
  276. end;
  277. procedure TTestCssHighlighter.TestCssMediaQuery;
  278. var
  279. tokens: TSyntaxTokenArray;
  280. mediaQuery: string;
  281. i: Integer;
  282. HasProperties,hasDirective, hasSelectors: Boolean;
  283. begin
  284. mediaQuery := '@media (max-width: 768px) { body { font-size: 14px; } }';
  285. tokens := DoCssHighlighting(mediaQuery);
  286. AssertTrue('Should have multiple tokens', Length(tokens) > 10);
  287. // Check that we have different token types
  288. hasDirective := False;
  289. hasSelectors := False;
  290. hasProperties := False;
  291. for i := 0 to High(tokens) do
  292. begin
  293. case tokens[i].Kind of
  294. shDirective: hasDirective := True;
  295. shDefault: hasSelectors := True;
  296. shKeyword: hasProperties := True;
  297. end;
  298. end;
  299. AssertTrue('Should contain directive tokens', hasDirective);
  300. AssertTrue('Should contain selector tokens', hasSelectors);
  301. // Note: Properties inside media queries may not be recognized as keywords
  302. // depending on the CSS highlighter's context-sensitivity implementation
  303. // First token should be @media directive
  304. AssertEquals('First token should be @media', '@media', tokens[0].Text);
  305. AssertEquals('First token should be directive', Ord(shDirective), Ord(tokens[0].Kind));
  306. end;
  307. procedure TTestCssHighlighter.TestCssUnits;
  308. var
  309. tokens: TSyntaxTokenArray;
  310. begin
  311. // Test various CSS units
  312. tokens := DoCssHighlighting('10rem');
  313. AssertEquals('Should have 1 token', 1, Length(tokens));
  314. AssertEquals('Token should be rem value', '10rem', tokens[0].Text);
  315. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  316. tokens := DoCssHighlighting('2vh');
  317. AssertEquals('Should have 1 token', 1, Length(tokens));
  318. AssertEquals('Token should be vh value', '2vh', tokens[0].Text);
  319. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  320. tokens := DoCssHighlighting('50vw');
  321. AssertEquals('Should have 1 token', 1, Length(tokens));
  322. AssertEquals('Token should be vw value', '50vw', tokens[0].Text);
  323. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  324. tokens := DoCssHighlighting('1.2fr');
  325. AssertEquals('Should have 1 token', 1, Length(tokens));
  326. AssertEquals('Token should be fr value', '1.2fr', tokens[0].Text);
  327. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  328. end;
  329. initialization
  330. RegisterTest(TTestCssHighlighter);
  331. end.