2
0

unittest.javascript.pp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2025 by Michael Van Canneyt
  4. Javascript 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.javascript;
  12. interface
  13. {$mode objfpc}{$H+}
  14. uses
  15. Classes, SysUtils, fpcunit, testregistry,
  16. syntax.highlighter, syntax.javascript;
  17. type
  18. TTestJavaScriptHighlighter = class(TTestCase)
  19. protected
  20. procedure SetUp; override;
  21. procedure TearDown; override;
  22. private
  23. function DoJavaScriptHighlighting(const source: string): TSyntaxTokenArray;
  24. published
  25. procedure TestJavaScriptKeywords;
  26. procedure TestJavaScriptStrings;
  27. procedure TestJavaScriptNumbers;
  28. procedure TestJavaScriptComments;
  29. procedure TestJavaScriptOperators;
  30. procedure TestJavaScriptSymbols;
  31. procedure TestJavaScriptIdentifiers;
  32. procedure TestJavaScriptRegexLiterals;
  33. procedure TestJavaScriptTemplateLiterals;
  34. procedure TestJavaScriptNumericFormats;
  35. procedure TestComplexJavaScriptFunction;
  36. procedure TestJavaScriptContextSensitive;
  37. end;
  38. implementation
  39. procedure TTestJavaScriptHighlighter.SetUp;
  40. begin
  41. end;
  42. procedure TTestJavaScriptHighlighter.TearDown;
  43. begin
  44. // Nothing to do
  45. end;
  46. function TTestJavaScriptHighlighter.DoJavaScriptHighlighting(const source: string): TSyntaxTokenArray;
  47. var
  48. highlighter: TJavaScriptSyntaxHighlighter;
  49. begin
  50. highlighter := TJavaScriptSyntaxHighlighter.Create;
  51. try
  52. Result := highlighter.Execute(source);
  53. finally
  54. highlighter.Free;
  55. end;
  56. end;
  57. procedure TTestJavaScriptHighlighter.TestJavaScriptKeywords;
  58. const
  59. Keywords: array[0..19] of string = (
  60. 'var', 'let', 'const', 'function', 'if', 'else', 'for', 'while', 'do', 'switch',
  61. 'case', 'default', 'break', 'continue', 'return', 'try', 'catch', 'finally', 'throw', 'new'
  62. );
  63. var
  64. tokens: TSyntaxTokenArray;
  65. i: Integer;
  66. begin
  67. for i := 0 to High(Keywords) do
  68. begin
  69. tokens := DoJavaScriptHighlighting(Keywords[i]);
  70. AssertEquals('Should have 1 token for ' + Keywords[i], 1, Length(tokens));
  71. AssertEquals('Token should be ' + Keywords[i], Keywords[i], tokens[0].Text);
  72. AssertEquals(Keywords[i] + ' should be keyword', Ord(shKeyword), Ord(tokens[0].Kind));
  73. end;
  74. end;
  75. procedure TTestJavaScriptHighlighter.TestJavaScriptStrings;
  76. var
  77. tokens: TSyntaxTokenArray;
  78. begin
  79. // Test single-quoted string
  80. tokens := DoJavaScriptHighlighting('''hello world''');
  81. AssertEquals('Should have 1 token', 1, Length(tokens));
  82. AssertEquals('Token should be single-quoted string', '''hello world''', tokens[0].Text);
  83. AssertEquals('Token should be string', Ord(shStrings), Ord(tokens[0].Kind));
  84. // Test double-quoted string
  85. tokens := DoJavaScriptHighlighting('"hello world"');
  86. AssertEquals('Should have 1 token', 1, Length(tokens));
  87. AssertEquals('Token should be double-quoted string', '"hello world"', tokens[0].Text);
  88. AssertEquals('Token should be string', Ord(shStrings), Ord(tokens[0].Kind));
  89. // Test string with escapes
  90. tokens := DoJavaScriptHighlighting('"hello\nworld"');
  91. AssertEquals('Should have 1 token', 1, Length(tokens));
  92. AssertEquals('Token should be escaped string', '"hello\nworld"', tokens[0].Text);
  93. AssertEquals('Token should be string', Ord(shStrings), Ord(tokens[0].Kind));
  94. end;
  95. procedure TTestJavaScriptHighlighter.TestJavaScriptNumbers;
  96. var
  97. tokens: TSyntaxTokenArray;
  98. begin
  99. // Test integer
  100. tokens := DoJavaScriptHighlighting('123');
  101. AssertEquals('Should have 1 token', 1, Length(tokens));
  102. AssertEquals('Token should be integer', '123', tokens[0].Text);
  103. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  104. // Test decimal
  105. tokens := DoJavaScriptHighlighting('123.45');
  106. AssertEquals('Should have 1 token', 1, Length(tokens));
  107. AssertEquals('Token should be decimal', '123.45', tokens[0].Text);
  108. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  109. // Test hex number
  110. tokens := DoJavaScriptHighlighting('0xFF');
  111. AssertEquals('Should have 1 token', 1, Length(tokens));
  112. AssertEquals('Token should be hex', '0xFF', tokens[0].Text);
  113. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  114. end;
  115. procedure TTestJavaScriptHighlighter.TestJavaScriptComments;
  116. var
  117. tokens: TSyntaxTokenArray;
  118. begin
  119. // Test single-line comment
  120. tokens := DoJavaScriptHighlighting('// This is a comment');
  121. AssertEquals('Should have 1 token', 1, Length(tokens));
  122. AssertEquals('Token should be single-line comment', '// This is a comment', tokens[0].Text);
  123. AssertEquals('Token should be comment', Ord(shComment), Ord(tokens[0].Kind));
  124. // Test multi-line comment
  125. tokens := DoJavaScriptHighlighting('/* This is a comment */');
  126. AssertEquals('Should have 1 token', 1, Length(tokens));
  127. AssertEquals('Token should be multi-line comment', '/* This is a comment */', tokens[0].Text);
  128. AssertEquals('Token should be comment', Ord(shComment), Ord(tokens[0].Kind));
  129. // Test multi-line comment with newlines
  130. tokens := DoJavaScriptHighlighting('/* Line 1' + #10 + 'Line 2 */');
  131. AssertEquals('Should have 1 token', 1, Length(tokens));
  132. AssertEquals('Token should be multi-line comment with newlines', '/* Line 1' + #10 + 'Line 2 */', tokens[0].Text);
  133. AssertEquals('Token should be comment', Ord(shComment), Ord(tokens[0].Kind));
  134. end;
  135. procedure TTestJavaScriptHighlighter.TestJavaScriptOperators;
  136. var
  137. tokens: TSyntaxTokenArray;
  138. begin
  139. // Test strict equality
  140. tokens := DoJavaScriptHighlighting('===');
  141. AssertEquals('Should have 1 token', 1, Length(tokens));
  142. AssertEquals('Token should be strict equality', '===', tokens[0].Text);
  143. AssertEquals('Token should be operator', Ord(shOperator), Ord(tokens[0].Kind));
  144. // Test inequality
  145. tokens := DoJavaScriptHighlighting('!==');
  146. AssertEquals('Should have 1 token', 1, Length(tokens));
  147. AssertEquals('Token should be strict inequality', '!==', tokens[0].Text);
  148. AssertEquals('Token should be operator', Ord(shOperator), Ord(tokens[0].Kind));
  149. // Test arrow function
  150. tokens := DoJavaScriptHighlighting('=>');
  151. AssertEquals('Should have 1 token', 1, Length(tokens));
  152. AssertEquals('Token should be arrow operator', '=>', tokens[0].Text);
  153. AssertEquals('Token should be operator', Ord(shOperator), Ord(tokens[0].Kind));
  154. // Test logical AND
  155. tokens := DoJavaScriptHighlighting('&&');
  156. AssertEquals('Should have 1 token', 1, Length(tokens));
  157. AssertEquals('Token should be logical AND', '&&', tokens[0].Text);
  158. AssertEquals('Token should be operator', Ord(shOperator), Ord(tokens[0].Kind));
  159. end;
  160. procedure TTestJavaScriptHighlighter.TestJavaScriptSymbols;
  161. var
  162. tokens: TSyntaxTokenArray;
  163. begin
  164. // Test parentheses
  165. tokens := DoJavaScriptHighlighting('(');
  166. AssertEquals('Should have 1 token', 1, Length(tokens));
  167. AssertEquals('Token should be opening paren', '(', tokens[0].Text);
  168. AssertEquals('Token should be symbol', Ord(shSymbol), Ord(tokens[0].Kind));
  169. // Test braces
  170. tokens := DoJavaScriptHighlighting('{');
  171. AssertEquals('Should have 1 token', 1, Length(tokens));
  172. AssertEquals('Token should be opening brace', '{', tokens[0].Text);
  173. AssertEquals('Token should be symbol', Ord(shSymbol), Ord(tokens[0].Kind));
  174. // Test semicolon
  175. tokens := DoJavaScriptHighlighting(';');
  176. AssertEquals('Should have 1 token', 1, Length(tokens));
  177. AssertEquals('Token should be semicolon', ';', tokens[0].Text);
  178. AssertEquals('Token should be symbol', Ord(shSymbol), Ord(tokens[0].Kind));
  179. // Test comma
  180. tokens := DoJavaScriptHighlighting(',');
  181. AssertEquals('Should have 1 token', 1, Length(tokens));
  182. AssertEquals('Token should be comma', ',', tokens[0].Text);
  183. AssertEquals('Token should be symbol', Ord(shSymbol), Ord(tokens[0].Kind));
  184. end;
  185. procedure TTestJavaScriptHighlighter.TestJavaScriptIdentifiers;
  186. var
  187. tokens: TSyntaxTokenArray;
  188. begin
  189. // Test simple identifier
  190. tokens := DoJavaScriptHighlighting('myVariable');
  191. AssertEquals('Should have 1 token', 1, Length(tokens));
  192. AssertEquals('Token should be identifier', 'myVariable', tokens[0].Text);
  193. AssertEquals('Token should be default', Ord(shDefault), Ord(tokens[0].Kind));
  194. // Test identifier with underscore
  195. tokens := DoJavaScriptHighlighting('_private');
  196. AssertEquals('Should have 1 token', 1, Length(tokens));
  197. AssertEquals('Token should be underscore identifier', '_private', tokens[0].Text);
  198. AssertEquals('Token should be default', Ord(shDefault), Ord(tokens[0].Kind));
  199. // Test identifier with dollar sign
  200. tokens := DoJavaScriptHighlighting('$element');
  201. AssertEquals('Should have 1 token', 1, Length(tokens));
  202. AssertEquals('Token should be dollar identifier', '$element', tokens[0].Text);
  203. AssertEquals('Token should be default', Ord(shDefault), Ord(tokens[0].Kind));
  204. end;
  205. procedure TTestJavaScriptHighlighter.TestJavaScriptRegexLiterals;
  206. var
  207. tokens: TSyntaxTokenArray;
  208. begin
  209. // Test simple regex
  210. tokens := DoJavaScriptHighlighting('/[a-z]+/gi');
  211. AssertEquals('Should have 1 token', 1, Length(tokens));
  212. AssertEquals('Token should be regex', '/[a-z]+/gi', tokens[0].Text);
  213. AssertEquals('Token should be regex type', Ord(shRegex), Ord(tokens[0].Kind));
  214. // Test regex with escape sequences
  215. tokens := DoJavaScriptHighlighting('/\\d+\\.\\d*/');
  216. AssertEquals('Should have 1 token', 1, Length(tokens));
  217. AssertEquals('Token should be escaped regex', '/\\d+\\.\\d*/', tokens[0].Text);
  218. AssertEquals('Token should be regex type', Ord(shRegex), Ord(tokens[0].Kind));
  219. end;
  220. procedure TTestJavaScriptHighlighter.TestJavaScriptTemplateLiterals;
  221. var
  222. tokens: TSyntaxTokenArray;
  223. begin
  224. // Test simple template literal
  225. tokens := DoJavaScriptHighlighting('`hello world`');
  226. AssertEquals('Should have 1 token', 1, Length(tokens));
  227. AssertEquals('Token should be template literal', '`hello world`', tokens[0].Text);
  228. AssertEquals('Token should be raw string type', Ord(shRawString), Ord(tokens[0].Kind));
  229. // Test template literal with interpolation
  230. tokens := DoJavaScriptHighlighting('`hello ${name}`');
  231. AssertEquals('Should have 1 token', 1, Length(tokens));
  232. AssertEquals('Token should be template with interpolation', '`hello ${name}`', tokens[0].Text);
  233. AssertEquals('Token should be raw string type', Ord(shRawString), Ord(tokens[0].Kind));
  234. end;
  235. procedure TTestJavaScriptHighlighter.TestJavaScriptNumericFormats;
  236. var
  237. tokens: TSyntaxTokenArray;
  238. begin
  239. // Test scientific notation
  240. tokens := DoJavaScriptHighlighting('1.23e-4');
  241. AssertEquals('Should have 1 token', 1, Length(tokens));
  242. AssertEquals('Token should be scientific notation', '1.23e-4', tokens[0].Text);
  243. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  244. // Test binary number
  245. tokens := DoJavaScriptHighlighting('0b1010');
  246. AssertEquals('Should have 1 token', 1, Length(tokens));
  247. AssertEquals('Token should be binary number', '0b1010', tokens[0].Text);
  248. AssertEquals('Token should be number', Ord(shNumbers), Ord(tokens[0].Kind));
  249. // Test octal number
  250. tokens := DoJavaScriptHighlighting('0o755');
  251. AssertTrue('Should have at least 1 token', Length(tokens) >= 1);
  252. AssertEquals('First token should be number type', Ord(shNumbers), Ord(tokens[0].Kind));
  253. end;
  254. procedure TTestJavaScriptHighlighter.TestComplexJavaScriptFunction;
  255. var
  256. tokens: TSyntaxTokenArray;
  257. jsCode: string;
  258. i: Integer;
  259. hasKeywords, hasStrings, hasSymbols, hasIdentifiers: Boolean;
  260. begin
  261. jsCode := 'function greet(name) { return `Hello ${name}!`; }';
  262. tokens := DoJavaScriptHighlighting(jsCode);
  263. AssertTrue('Should have multiple tokens', Length(tokens) > 5);
  264. // Check that we have different token types
  265. hasKeywords := False;
  266. hasStrings := False;
  267. hasSymbols := False;
  268. hasIdentifiers := False;
  269. for i := 0 to High(tokens) do
  270. case tokens[i].Kind of
  271. shKeyword: hasKeywords := True;
  272. shRawString: hasStrings := True;
  273. shSymbol: hasSymbols := True;
  274. shDefault: hasIdentifiers := True;
  275. end;
  276. AssertTrue('Should contain keyword tokens', hasKeywords);
  277. AssertTrue('Should contain string tokens', hasStrings);
  278. AssertTrue('Should contain symbol tokens', hasSymbols);
  279. AssertTrue('Should contain identifier tokens', hasIdentifiers);
  280. // First token should be 'function' keyword
  281. AssertEquals('First token should be function', 'function', tokens[0].Text);
  282. AssertEquals('First token should be keyword', Ord(shKeyword), Ord(tokens[0].Kind));
  283. // Should contain template literal
  284. for i := 0 to High(tokens) do
  285. if tokens[i].Kind = shRawString then
  286. begin
  287. AssertEquals('Should have template literal', '`Hello ${name}!`', tokens[i].Text);
  288. Break;
  289. end;
  290. end;
  291. procedure TTestJavaScriptHighlighter.TestJavaScriptContextSensitive;
  292. var
  293. tokens: TSyntaxTokenArray;
  294. begin
  295. // Test that context-sensitive features work at least partially
  296. // Note: Full context sensitivity for regex vs division is complex
  297. // and may not be fully implemented in all cases
  298. // Test assignment context - this should work well
  299. tokens := DoJavaScriptHighlighting('var x = 42;');
  300. AssertTrue('Should have multiple tokens', Length(tokens) >= 5);
  301. // Should have var keyword
  302. AssertEquals('First token should be var', 'var', tokens[0].Text);
  303. AssertEquals('First token should be keyword', Ord(shKeyword), Ord(tokens[0].Kind));
  304. // Should have identifier
  305. AssertEquals('Second token should be space', ' ', tokens[1].Text);
  306. AssertEquals('Third token should be identifier', 'x', tokens[2].Text);
  307. AssertEquals('Third token should be default', Ord(shDefault), Ord(tokens[2].Kind));
  308. // Should have assignment and number
  309. AssertEquals('Fourth token should be space', ' ', tokens[3].Text);
  310. AssertEquals('Fifth token should be assignment', '=', tokens[4].Text);
  311. AssertEquals('Fifth token should be operator', Ord(shOperator), Ord(tokens[4].Kind));
  312. end;
  313. initialization
  314. RegisterTest(TTestJavaScriptHighlighter);
  315. end.