unittest.json.pp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2025 by Michael Van Canneyt
  4. JSON 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.json;
  12. interface
  13. {$mode objfpc}{$H+}
  14. uses
  15. Classes, SysUtils, fpcunit, testregistry,
  16. syntax.highlighter, syntax.json;
  17. type
  18. TTestJsonHighlighter = class(TTestCase)
  19. protected
  20. procedure SetUp; override;
  21. procedure TearDown; override;
  22. private
  23. function DoJsonHighlighting(const source: string): TSyntaxTokenArray;
  24. published
  25. procedure TestJsonKeys;
  26. procedure TestJsonStrings;
  27. procedure TestJsonNumbers;
  28. procedure TestJsonKeywords;
  29. procedure TestJsonSymbols;
  30. procedure TestJsonEscapeSequences;
  31. procedure TestJsonUnicodeEscapes;
  32. procedure TestJsonInvalidStrings;
  33. procedure TestJsonInvalidNumbers;
  34. procedure TestComplexJsonObject;
  35. procedure TestJsonArray;
  36. procedure TestNestedJsonStructures;
  37. procedure TestJsonWhitespace;
  38. procedure TestJsonScientificNotation;
  39. procedure TestJsonEdgeCases;
  40. procedure TestJsonEmpty;
  41. end;
  42. implementation
  43. procedure TTestJsonHighlighter.SetUp;
  44. begin
  45. end;
  46. procedure TTestJsonHighlighter.TearDown;
  47. begin
  48. // Nothing to do
  49. end;
  50. function TTestJsonHighlighter.DoJsonHighlighting(const source: string): TSyntaxTokenArray;
  51. var
  52. highlighter: TJsonSyntaxHighlighter;
  53. begin
  54. highlighter := TJsonSyntaxHighlighter.Create;
  55. try
  56. Result := highlighter.Execute(source);
  57. finally
  58. highlighter.Free;
  59. end;
  60. end;
  61. procedure TTestJsonHighlighter.TestJsonKeys;
  62. var
  63. tokens: TSyntaxTokenArray;
  64. i: Integer;
  65. foundKey: Boolean;
  66. begin
  67. // Test simple object with key
  68. tokens := DoJsonHighlighting('{"name": "value"}');
  69. foundKey := False;
  70. for i := 0 to High(tokens) do
  71. begin
  72. if (tokens[i].Text = '"name"') and (tokens[i].Kind = shKey) then
  73. begin
  74. foundKey := True;
  75. break;
  76. end;
  77. end;
  78. AssertTrue('Should find key token', foundKey);
  79. // Test multiple keys
  80. tokens := DoJsonHighlighting('{"firstName": "John", "lastName": "Doe"}');
  81. foundKey := False;
  82. for i := 0 to High(tokens) do
  83. begin
  84. if (tokens[i].Text = '"firstName"') and (tokens[i].Kind = shKey) then
  85. begin
  86. foundKey := True;
  87. break;
  88. end;
  89. end;
  90. AssertTrue('Should find firstName key', foundKey);
  91. end;
  92. procedure TTestJsonHighlighter.TestJsonStrings;
  93. var
  94. tokens: TSyntaxTokenArray;
  95. i: Integer;
  96. foundString: Boolean;
  97. begin
  98. // Test string value (not key)
  99. tokens := DoJsonHighlighting('{"name": "John Doe"}');
  100. foundString := False;
  101. for i := 0 to High(tokens) do
  102. begin
  103. if (tokens[i].Text = '"John Doe"') and (tokens[i].Kind = shStrings) then
  104. begin
  105. foundString := True;
  106. break;
  107. end;
  108. end;
  109. AssertTrue('Should find string value token', foundString);
  110. // Test empty string
  111. tokens := DoJsonHighlighting('{"empty": ""}');
  112. foundString := False;
  113. for i := 0 to High(tokens) do
  114. begin
  115. if (tokens[i].Text = '""') and (tokens[i].Kind = shStrings) then
  116. begin
  117. foundString := True;
  118. break;
  119. end;
  120. end;
  121. AssertTrue('Should find empty string token', foundString);
  122. end;
  123. procedure TTestJsonHighlighter.TestJsonNumbers;
  124. var
  125. tokens: TSyntaxTokenArray;
  126. i: Integer;
  127. foundNumber: Boolean;
  128. begin
  129. // Test integer
  130. tokens := DoJsonHighlighting('{"age": 25}');
  131. foundNumber := False;
  132. for i := 0 to High(tokens) do
  133. begin
  134. if (tokens[i].Text = '25') and (tokens[i].Kind = shNumbers) then
  135. begin
  136. foundNumber := True;
  137. break;
  138. end;
  139. end;
  140. AssertTrue('Should find integer number', foundNumber);
  141. // Test decimal
  142. tokens := DoJsonHighlighting('{"price": 19.99}');
  143. foundNumber := False;
  144. for i := 0 to High(tokens) do
  145. begin
  146. if (tokens[i].Text = '19.99') and (tokens[i].Kind = shNumbers) then
  147. begin
  148. foundNumber := True;
  149. break;
  150. end;
  151. end;
  152. AssertTrue('Should find decimal number', foundNumber);
  153. // Test negative number
  154. tokens := DoJsonHighlighting('{"temperature": -15}');
  155. foundNumber := False;
  156. for i := 0 to High(tokens) do
  157. begin
  158. if (tokens[i].Text = '-15') and (tokens[i].Kind = shNumbers) then
  159. begin
  160. foundNumber := True;
  161. break;
  162. end;
  163. end;
  164. AssertTrue('Should find negative number', foundNumber);
  165. // Test zero
  166. tokens := DoJsonHighlighting('{"zero": 0}');
  167. foundNumber := False;
  168. for i := 0 to High(tokens) do
  169. begin
  170. if (tokens[i].Text = '0') and (tokens[i].Kind = shNumbers) then
  171. begin
  172. foundNumber := True;
  173. break;
  174. end;
  175. end;
  176. AssertTrue('Should find zero', foundNumber);
  177. end;
  178. procedure TTestJsonHighlighter.TestJsonKeywords;
  179. var
  180. tokens: TSyntaxTokenArray;
  181. i: Integer;
  182. foundKeyword: Boolean;
  183. begin
  184. // Test true
  185. tokens := DoJsonHighlighting('{"valid": true}');
  186. foundKeyword := False;
  187. for i := 0 to High(tokens) do
  188. begin
  189. if (tokens[i].Text = 'true') and (tokens[i].Kind = shKeyword) then
  190. begin
  191. foundKeyword := True;
  192. break;
  193. end;
  194. end;
  195. AssertTrue('Should find true keyword', foundKeyword);
  196. // Test false
  197. tokens := DoJsonHighlighting('{"valid": false}');
  198. foundKeyword := False;
  199. for i := 0 to High(tokens) do
  200. begin
  201. if (tokens[i].Text = 'false') and (tokens[i].Kind = shKeyword) then
  202. begin
  203. foundKeyword := True;
  204. break;
  205. end;
  206. end;
  207. AssertTrue('Should find false keyword', foundKeyword);
  208. // Test null
  209. tokens := DoJsonHighlighting('{"value": null}');
  210. foundKeyword := False;
  211. for i := 0 to High(tokens) do
  212. begin
  213. if (tokens[i].Text = 'null') and (tokens[i].Kind = shKeyword) then
  214. begin
  215. foundKeyword := True;
  216. break;
  217. end;
  218. end;
  219. AssertTrue('Should find null keyword', foundKeyword);
  220. end;
  221. procedure TTestJsonHighlighter.TestJsonSymbols;
  222. var
  223. tokens: TSyntaxTokenArray;
  224. i: Integer;
  225. foundSymbol: Boolean;
  226. begin
  227. tokens := DoJsonHighlighting('{"key": "value"}');
  228. // Test opening brace
  229. foundSymbol := False;
  230. for i := 0 to High(tokens) do
  231. begin
  232. if (tokens[i].Text = '{') and (tokens[i].Kind = shSymbol) then
  233. begin
  234. foundSymbol := True;
  235. break;
  236. end;
  237. end;
  238. AssertTrue('Should find opening brace', foundSymbol);
  239. // Test colon
  240. foundSymbol := False;
  241. for i := 0 to High(tokens) do
  242. begin
  243. if (tokens[i].Text = ':') and (tokens[i].Kind = shSymbol) then
  244. begin
  245. foundSymbol := True;
  246. break;
  247. end;
  248. end;
  249. AssertTrue('Should find colon', foundSymbol);
  250. // Test closing brace
  251. foundSymbol := False;
  252. for i := 0 to High(tokens) do
  253. begin
  254. if (tokens[i].Text = '}') and (tokens[i].Kind = shSymbol) then
  255. begin
  256. foundSymbol := True;
  257. break;
  258. end;
  259. end;
  260. AssertTrue('Should find closing brace', foundSymbol);
  261. end;
  262. procedure TTestJsonHighlighter.TestJsonEscapeSequences;
  263. var
  264. tokens: TSyntaxTokenArray;
  265. i: Integer;
  266. foundString: Boolean;
  267. begin
  268. // Test basic escape sequences
  269. tokens := DoJsonHighlighting('{"message": "Hello\nWorld"}');
  270. foundString := False;
  271. for i := 0 to High(tokens) do
  272. begin
  273. if (tokens[i].Text = '"Hello\nWorld"') and (tokens[i].Kind = shStrings) then
  274. begin
  275. foundString := True;
  276. break;
  277. end;
  278. end;
  279. AssertTrue('Should handle newline escape', foundString);
  280. // Test quote escape
  281. tokens := DoJsonHighlighting('{"quote": "Say \"Hello\""}');
  282. foundString := False;
  283. for i := 0 to High(tokens) do
  284. begin
  285. if (tokens[i].Text = '"Say \"Hello\""') and (tokens[i].Kind = shStrings) then
  286. begin
  287. foundString := True;
  288. break;
  289. end;
  290. end;
  291. AssertTrue('Should handle quote escape', foundString);
  292. // Test backslash escape
  293. tokens := DoJsonHighlighting('{"path": "C:\\Windows"}');
  294. foundString := False;
  295. for i := 0 to High(tokens) do
  296. begin
  297. if (tokens[i].Text = '"C:\\Windows"') and (tokens[i].Kind = shStrings) then
  298. begin
  299. foundString := True;
  300. break;
  301. end;
  302. end;
  303. AssertTrue('Should handle backslash escape', foundString);
  304. end;
  305. procedure TTestJsonHighlighter.TestJsonUnicodeEscapes;
  306. var
  307. tokens: TSyntaxTokenArray;
  308. i: Integer;
  309. foundString: Boolean;
  310. begin
  311. // Test unicode escape
  312. tokens := DoJsonHighlighting('{"unicode": "\u0048\u0065\u006C\u006C\u006F"}');
  313. foundString := False;
  314. for i := 0 to High(tokens) do
  315. begin
  316. if (tokens[i].Text = '"\u0048\u0065\u006C\u006C\u006F"') and (tokens[i].Kind = shStrings) then
  317. begin
  318. foundString := True;
  319. break;
  320. end;
  321. end;
  322. AssertTrue('Should handle unicode escapes', foundString);
  323. end;
  324. procedure TTestJsonHighlighter.TestJsonInvalidStrings;
  325. var
  326. tokens: TSyntaxTokenArray;
  327. i: Integer;
  328. foundInvalid: Boolean;
  329. begin
  330. // Test invalid escape sequence
  331. tokens := DoJsonHighlighting('{"invalid": "\x"}');
  332. foundInvalid := False;
  333. for i := 0 to High(tokens) do
  334. begin
  335. if tokens[i].Kind = shInvalid then
  336. begin
  337. foundInvalid := True;
  338. break;
  339. end;
  340. end;
  341. AssertTrue('Should find invalid escape sequence', foundInvalid);
  342. // Test unterminated string
  343. tokens := DoJsonHighlighting('{"unterminated": "hello');
  344. foundInvalid := False;
  345. for i := 0 to High(tokens) do
  346. begin
  347. if tokens[i].Kind = shInvalid then
  348. begin
  349. foundInvalid := True;
  350. break;
  351. end;
  352. end;
  353. AssertTrue('Should find unterminated string', foundInvalid);
  354. end;
  355. procedure TTestJsonHighlighter.TestJsonInvalidNumbers;
  356. var
  357. tokens: TSyntaxTokenArray;
  358. i: Integer;
  359. foundInvalid: Boolean;
  360. begin
  361. // Test leading zero (invalid in JSON)
  362. tokens := DoJsonHighlighting('{"invalid": 01}');
  363. foundInvalid := False;
  364. for i := 0 to High(tokens) do
  365. begin
  366. if tokens[i].Kind = shInvalid then
  367. begin
  368. foundInvalid := True;
  369. break;
  370. end;
  371. end;
  372. AssertTrue('Should find invalid leading zero', foundInvalid);
  373. // Test incomplete decimal
  374. tokens := DoJsonHighlighting('{"incomplete": 12.}');
  375. foundInvalid := False;
  376. for i := 0 to High(tokens) do
  377. begin
  378. if tokens[i].Kind = shInvalid then
  379. begin
  380. foundInvalid := True;
  381. break;
  382. end;
  383. end;
  384. AssertTrue('Should find incomplete decimal', foundInvalid);
  385. end;
  386. procedure TTestJsonHighlighter.TestComplexJsonObject;
  387. var
  388. tokens: TSyntaxTokenArray;
  389. jsonText: string;
  390. i: Integer;
  391. hasKeys, hasStrings, hasNumbers, hasKeywords, hasSymbols: Boolean;
  392. begin
  393. jsonText := '{"name": "John", "age": 30, "married": true, "children": null}';
  394. tokens := DoJsonHighlighting(jsonText);
  395. AssertTrue('Should have multiple tokens', Length(tokens) > 10);
  396. // Check for different token types
  397. hasKeys := False;
  398. hasStrings := False;
  399. hasNumbers := False;
  400. hasKeywords := False;
  401. hasSymbols := False;
  402. for i := 0 to High(tokens) do
  403. begin
  404. case tokens[i].Kind of
  405. shKey: hasKeys := True;
  406. shStrings: hasStrings := True;
  407. shNumbers: hasNumbers := True;
  408. shKeyword: hasKeywords := True;
  409. shSymbol: hasSymbols := True;
  410. end;
  411. end;
  412. AssertTrue('Should contain key tokens', hasKeys);
  413. AssertTrue('Should contain string tokens', hasStrings);
  414. AssertTrue('Should contain number tokens', hasNumbers);
  415. AssertTrue('Should contain keyword tokens', hasKeywords);
  416. AssertTrue('Should contain symbol tokens', hasSymbols);
  417. end;
  418. procedure TTestJsonHighlighter.TestJsonArray;
  419. var
  420. tokens: TSyntaxTokenArray;
  421. i: Integer;
  422. foundBrackets: Boolean;
  423. begin
  424. tokens := DoJsonHighlighting('[1, 2, 3]');
  425. foundBrackets := False;
  426. for i := 0 to High(tokens) do
  427. begin
  428. if (tokens[i].Text = '[') and (tokens[i].Kind = shSymbol) then
  429. begin
  430. foundBrackets := True;
  431. break;
  432. end;
  433. end;
  434. AssertTrue('Should find opening bracket', foundBrackets);
  435. foundBrackets := False;
  436. for i := 0 to High(tokens) do
  437. begin
  438. if (tokens[i].Text = ']') and (tokens[i].Kind = shSymbol) then
  439. begin
  440. foundBrackets := True;
  441. break;
  442. end;
  443. end;
  444. AssertTrue('Should find closing bracket', foundBrackets);
  445. end;
  446. procedure TTestJsonHighlighter.TestNestedJsonStructures;
  447. var
  448. tokens: TSyntaxTokenArray;
  449. jsonText: string;
  450. i: Integer;
  451. hasKeys, hasNumbers: Boolean;
  452. begin
  453. jsonText := '{"person": {"name": "John", "address": {"city": "New York", "zip": 10001}}}';
  454. tokens := DoJsonHighlighting(jsonText);
  455. AssertTrue('Should handle nested structures', Length(tokens) > 15);
  456. // Verify we have both keys and numbers in nested structure
  457. hasKeys := False;
  458. hasNumbers := False;
  459. for i := 0 to High(tokens) do
  460. begin
  461. case tokens[i].Kind of
  462. shKey: hasKeys := True;
  463. shNumbers: hasNumbers := True;
  464. end;
  465. end;
  466. AssertTrue('Should contain keys in nested structure', hasKeys);
  467. AssertTrue('Should contain numbers in nested structure', hasNumbers);
  468. end;
  469. procedure TTestJsonHighlighter.TestJsonWhitespace;
  470. var
  471. tokens: TSyntaxTokenArray;
  472. jsonText: string;
  473. i: Integer;
  474. foundWhitespace: Boolean;
  475. begin
  476. jsonText := '{' + #10 + ' "name": "John"' + #10 + '}';
  477. tokens := DoJsonHighlighting(jsonText);
  478. foundWhitespace := False;
  479. for i := 0 to High(tokens) do
  480. begin
  481. if tokens[i].Kind = shDefault then
  482. begin
  483. foundWhitespace := True;
  484. break;
  485. end;
  486. end;
  487. AssertTrue('Should handle whitespace', foundWhitespace);
  488. end;
  489. procedure TTestJsonHighlighter.TestJsonScientificNotation;
  490. var
  491. tokens: TSyntaxTokenArray;
  492. i: Integer;
  493. foundNumber: Boolean;
  494. begin
  495. // Test scientific notation
  496. tokens := DoJsonHighlighting('{"scientific": 1.23e-4}');
  497. foundNumber := False;
  498. for i := 0 to High(tokens) do
  499. begin
  500. if (tokens[i].Text = '1.23e-4') and (tokens[i].Kind = shNumbers) then
  501. begin
  502. foundNumber := True;
  503. break;
  504. end;
  505. end;
  506. AssertTrue('Should handle scientific notation', foundNumber);
  507. // Test with capital E
  508. tokens := DoJsonHighlighting('{"scientific": 2.5E+10}');
  509. foundNumber := False;
  510. for i := 0 to High(tokens) do
  511. begin
  512. if (tokens[i].Text = '2.5E+10') and (tokens[i].Kind = shNumbers) then
  513. begin
  514. foundNumber := True;
  515. break;
  516. end;
  517. end;
  518. AssertTrue('Should handle capital E notation', foundNumber);
  519. end;
  520. procedure TTestJsonHighlighter.TestJsonEdgeCases;
  521. var
  522. tokens: TSyntaxTokenArray;
  523. i: Integer;
  524. foundKey, foundString: Boolean;
  525. begin
  526. // Test single character key and value
  527. tokens := DoJsonHighlighting('{"a": "b"}');
  528. foundKey := False;
  529. foundString := False;
  530. for i := 0 to High(tokens) do
  531. begin
  532. if (tokens[i].Text = '"a"') and (tokens[i].Kind = shKey) then
  533. foundKey := True;
  534. if (tokens[i].Text = '"b"') and (tokens[i].Kind = shStrings) then
  535. foundString := True;
  536. end;
  537. AssertTrue('Should handle single character key', foundKey);
  538. AssertTrue('Should handle single character string', foundString);
  539. end;
  540. procedure TTestJsonHighlighter.TestJsonEmpty;
  541. var
  542. tokens: TSyntaxTokenArray;
  543. begin
  544. // Test empty object
  545. tokens := DoJsonHighlighting('{}');
  546. AssertTrue('Should handle empty object', Length(tokens) >= 2);
  547. // Test empty array
  548. tokens := DoJsonHighlighting('[]');
  549. AssertTrue('Should handle empty array', Length(tokens) >= 2);
  550. end;
  551. initialization
  552. RegisterTest(TTestJsonHighlighter);
  553. end.