tccssparser.pp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  1. {
  2. This file is part of the Free Pascal Run time library.
  3. Copyright (c) 2022 by Michael Van Canneyt ([email protected])
  4. This file contains the tests for the CSS parser
  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 tcCSSParser;
  12. {$mode objfpc}{$H+}
  13. interface
  14. uses
  15. Classes, SysUtils, fpcunit, testregistry, fpcssparser, fpcsstree,
  16. fpCSSScanner;
  17. type
  18. { TTestBaseCSSParser }
  19. TTestBaseCSSParser = class(TTestCase)
  20. Private
  21. FParseResult: TCSSElement;
  22. FSkipInvalid: boolean;
  23. FSource : TStringStream;
  24. FParser : TCSSParser;
  25. FToFree: TCSSElement;
  26. procedure Clear;
  27. function GetRule: TCSSRuleElement;
  28. procedure OnScannerWarn(Sender: TObject; Msg: string);
  29. protected
  30. procedure SetUp; override;
  31. procedure TearDown; override;
  32. Procedure CreateParser(Const ASource : string);
  33. procedure Parse;
  34. procedure Parse(Const aSource : String);
  35. function ParseRule(Const aSource : String) : TCSSRuleElement;
  36. procedure AssertEquals(AMessage: String; AExpected, AActual: TCSSUnit); overload;
  37. procedure AssertEquals(AMessage: String; AExpected, AActual: TCSSBinaryOperation); overload;
  38. Function CheckClass(Const aMsg : String; aExpectedClass : TCSSElementClass; aActual : TCSSElement) : TCSSElement;
  39. Function CheckDeclaration(aRule : TCSSRuleElement; aIndex : Integer) : TCSSDeclarationElement;
  40. Function CheckDeclaration(aRule : TCSSRuleElement; aIndex : Integer; const AKey : String) : TCSSDeclarationElement;
  41. Function CheckSelector(aRule : TCSSRuleElement; aIndex : Integer) : TCSSElement;
  42. Function CheckSelector(aRule : TCSSRuleElement; aIndex : Integer; const aName : String) : TCSSElement;
  43. function CheckList(aList: TCSSListElement; aIndex: Integer): TCSSElement;
  44. function CheckList(aList: TCSSListElement; aIndex: Integer; const aName: String): TCSSElement;
  45. function CheckLiteral(Msg: String; aEl: TCSSelement; aValue: String) : TCSSStringElement; overload;
  46. function CheckLiteral(Msg: String; aEl: TCSSelement; aValue: Integer) : TCSSIntegerElement; overload;
  47. function CheckLiteral(Msg: String; aEl: TCSSelement; aValue: Integer; AUnits : TCSSUnit) : TCSSIntegerElement; overload;
  48. Function GetCalArg(aCall : TCSSCallElement; aIndex : Integer) : TCSSElement;
  49. Public
  50. Property ParseResult : TCSSElement read FParseResult;
  51. Property FirstRule : TCSSRuleElement Read GetRule;
  52. Property ToFree : TCSSElement Read FToFree Write FToFree;
  53. Property SkipInvalid: boolean read FSkipInvalid write FSkipInvalid;
  54. end;
  55. { TTestCSSParser }
  56. TTestCSSParser = class(TTestBaseCSSParser)
  57. private
  58. Published
  59. Procedure TestEmpty;
  60. Procedure TestEmptyRule;
  61. Procedure TestPrefixedEmptyRule;
  62. Procedure TestClassPrefixedEmptyRule;
  63. Procedure TestHashPrefixedEmptyRule;
  64. procedure TestDescendantPrefixedEmptyRule;
  65. procedure TestDescendantMixedPrefixedEmptyRule;
  66. procedure TestAttributePrefixedEmptyRule;
  67. procedure TestAttributeSquaredEqualRule;
  68. procedure TestAttributePipeEqualRule;
  69. procedure TestAttributeStarEqualRule;
  70. procedure TestAttributeDollarEqualRule;
  71. procedure TestAttributeTildeEqualRule;
  72. procedure TestPseudoPrefixedEmptyRule;
  73. procedure TestPseudoFunctionEmptyRule;
  74. procedure TestFuncPrefixedEmptyRule;
  75. procedure TestQueryPrefixedEmptyRule;
  76. Procedure TestCommaPrefixedEmptyRule;
  77. Procedure TestOneDeclarationIDValue;
  78. Procedure TestOneDeclarationIDValueAndEmpty;
  79. Procedure TestOneDeclarationIntValue;
  80. Procedure TestOneDeclarationStringValue;
  81. Procedure TestOneDeclarationHashValue;
  82. Procedure TestOneDeclarationURLValue;
  83. Procedure TestOneDeclarationFloatValue;
  84. Procedure TestOneDeclarationMultiValue;
  85. Procedure TestOneDeclarationMultiListValue;
  86. Procedure TestOneDeclarationExprValue;
  87. Procedure TestOneDeclarationUnicodeRangeValue;
  88. Procedure TestOneDeclarationNoColon;
  89. Procedure TestTwoDeclarationNoColon;
  90. Procedure TestOneEmptyDeclaration;
  91. Procedure TestImportAtKeyWord;
  92. Procedure TestMediaPrint;
  93. Procedure TestSupportsFunction;
  94. Procedure TestSkipUnknownFunction;
  95. end;
  96. { TTestCSSFilesParser }
  97. TTestCSSFilesParser = class(TTestBaseCSSParser)
  98. private
  99. FTestDir: String;
  100. procedure SetTestDir(AValue: String);
  101. Public
  102. Procedure SetUp;override;
  103. Procedure RunFileTest(aFile : String='');
  104. Property TestDir : String Read FTestDir Write SetTestDir;
  105. Published
  106. // lowercase name must match 'test'+filename
  107. Procedure Testabsolute;
  108. Procedure Testanimation;
  109. Procedure Testanon;
  110. Procedure Testbigbig;
  111. Procedure Testclass;
  112. Procedure Testcolor;
  113. Procedure Testfont_face;
  114. Procedure Testfont_face2;
  115. Procedure Testfont;
  116. Procedure Testhello;
  117. Procedure Testid;
  118. Procedure Testinput_type;
  119. Procedure Testmargin;
  120. Procedure Testmedia_query;
  121. Procedure Testmystyle;
  122. Procedure Testnews;
  123. Procedure Testpadding;
  124. Procedure Teststyle;
  125. Procedure Teststyle2;
  126. Procedure Teststyle_big;
  127. Procedure TesTwildcard;
  128. end;
  129. implementation
  130. uses inifiles, typinfo;
  131. { TTestCSSFilesParser }
  132. procedure TTestCSSFilesParser.SetTestDir(AValue: String);
  133. begin
  134. if FTestDir=AValue then Exit;
  135. FTestDir:=AValue;
  136. end;
  137. procedure TTestCSSFilesParser.SetUp;
  138. begin
  139. inherited SetUp;
  140. With TMemIniFile.Create(ChangeFileExt(Paramstr(0),TCSSString('.ini'))) do
  141. try
  142. TestDir:=ReadString('CSS','SourceDir','css');
  143. finally
  144. Free
  145. end;
  146. end;
  147. procedure TTestCSSFilesParser.RunFileTest(aFile: String);
  148. var
  149. fn : string;
  150. OK : Boolean;
  151. begin
  152. if Afile='' then
  153. begin
  154. aFile:=LowerCase(TestName);
  155. if Copy(aFile,1,4)='test' then
  156. Delete(aFile,1,4);
  157. end;
  158. OK:=False;
  159. With TStringList.Create do
  160. try
  161. fn:=IncludeTrailingPathDelimiter(TestDir)+aFile+'.css';
  162. fn:=ExpandFileName(FN);
  163. // Writeln('Analysing file ',FN);
  164. LoadFromFile(fn);
  165. Parse(Text);
  166. OK:=True;
  167. finally
  168. if not OK then
  169. begin
  170. Writeln('Source generating error: ',FN);
  171. Writeln(Text);
  172. end;
  173. Free;
  174. end;
  175. end;
  176. procedure TTestCSSFilesParser.Testabsolute;
  177. begin
  178. RunFileTest;
  179. end;
  180. procedure TTestCSSFilesParser.Testanimation;
  181. begin
  182. SkipInvalid:=true;
  183. RunFileTest;
  184. end;
  185. procedure TTestCSSFilesParser.Testanon;
  186. begin
  187. RunFileTest;
  188. end;
  189. procedure TTestCSSFilesParser.Testbigbig;
  190. begin
  191. RunFileTest;
  192. end;
  193. procedure TTestCSSFilesParser.Testclass;
  194. begin
  195. RunFileTest;
  196. end;
  197. procedure TTestCSSFilesParser.Testcolor;
  198. begin
  199. RunFileTest;
  200. end;
  201. procedure TTestCSSFilesParser.Testfont_face;
  202. begin
  203. RunFileTest('font-face');
  204. end;
  205. procedure TTestCSSFilesParser.Testfont_face2;
  206. begin
  207. RunFileTest('font-face2');
  208. end;
  209. procedure TTestCSSFilesParser.Testfont;
  210. begin
  211. RunFileTest;
  212. end;
  213. procedure TTestCSSFilesParser.Testhello;
  214. begin
  215. RunFileTest;
  216. end;
  217. procedure TTestCSSFilesParser.Testid;
  218. begin
  219. RunFileTest;
  220. end;
  221. procedure TTestCSSFilesParser.Testinput_type;
  222. begin
  223. RunFileTest;
  224. end;
  225. procedure TTestCSSFilesParser.Testmargin;
  226. begin
  227. RunFileTest;
  228. end;
  229. procedure TTestCSSFilesParser.Testmedia_query;
  230. begin
  231. RunFileTest;
  232. end;
  233. procedure TTestCSSFilesParser.Testmystyle;
  234. begin
  235. RunFileTest;
  236. end;
  237. procedure TTestCSSFilesParser.Testnews;
  238. begin
  239. RunFileTest;
  240. end;
  241. procedure TTestCSSFilesParser.Testpadding;
  242. begin
  243. RunFileTest;
  244. end;
  245. procedure TTestCSSFilesParser.Teststyle;
  246. begin
  247. RunFileTest;
  248. end;
  249. procedure TTestCSSFilesParser.Teststyle2;
  250. begin
  251. RunFileTest;
  252. end;
  253. procedure TTestCSSFilesParser.Teststyle_big;
  254. begin
  255. RunFileTest;
  256. end;
  257. procedure TTestCSSFilesParser.TesTwildcard;
  258. begin
  259. RunFileTest;
  260. end;
  261. { TTestCSSParser }
  262. procedure TTestCSSParser.TestEmpty;
  263. var
  264. L : TCSSCompoundElement;
  265. begin
  266. Parse('');
  267. L:=TCSSCompoundElement(CheckClass('list',TCSSCompoundElement,ParseResult));
  268. AssertEquals('No children',0,L.ChildCount);
  269. end;
  270. procedure TTestCSSParser.TestEmptyRule;
  271. var
  272. R : TCSSRuleElement;
  273. begin
  274. R:=ParseRule('{}');
  275. AssertEquals('No rule children',0,R.ChildCount);
  276. end;
  277. procedure TTestCSSParser.TestPrefixedEmptyRule;
  278. var
  279. R : TCSSRuleElement;
  280. sel: TCSSIdentifierElement;
  281. begin
  282. ParseRule('a { }');
  283. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  284. AssertEquals('No rule children',0,R.ChildCount);
  285. AssertEquals('selector count',1,R.SelectorCount);
  286. sel:=TCSSIdentifierElement(CheckClass('Selector', TCSSIdentifierElement,R.Selectors[0]));
  287. AssertEquals('Sel name','a',Sel.Value);
  288. end;
  289. procedure TTestCSSParser.TestClassPrefixedEmptyRule;
  290. var
  291. R : TCSSRuleElement;
  292. sel: TCSSClassNameElement;
  293. begin
  294. ParseRule('.a { }');
  295. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  296. AssertEquals('No rule children',0,R.ChildCount);
  297. AssertEquals('selector count',1,R.SelectorCount);
  298. sel:=TCSSClassNameElement(CheckClass('Selector', TCSSClassNameElement,R.Selectors[0]));
  299. AssertEquals('Sel name','a',Sel.Value);
  300. end;
  301. procedure TTestCSSParser.TestHashPrefixedEmptyRule;
  302. var
  303. R : TCSSRuleElement;
  304. sel: TCSSHashIdentifierElement;
  305. begin
  306. ParseRule('#a { }');
  307. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  308. AssertEquals('No rule children',0,R.ChildCount);
  309. AssertEquals('selector count',1,R.SelectorCount);
  310. sel:=TCSSHashIdentifierElement(CheckClass('Selector', TCSSHashIdentifierElement,R.Selectors[0]));
  311. AssertEquals('Sel name','a',Sel.Value);
  312. end;
  313. procedure TTestCSSParser.TestDescendantPrefixedEmptyRule;
  314. var
  315. R : TCSSRuleElement;
  316. sel: TCSSIdentifierElement;
  317. Bin: TCSSBinaryElement;
  318. begin
  319. ParseRule('a b { }');
  320. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  321. AssertEquals('No rule children',0,R.ChildCount);
  322. AssertEquals('selector count',1,R.SelectorCount);
  323. Bin:=TCSSBinaryElement(CheckClass('Selector', TCSSBinaryElement,R.Selectors[0]));
  324. sel:=TCSSIdentifierElement(CheckClass('Selector', TCSSIdentifierElement,Bin.Left));
  325. AssertEquals('Sel 1 name','a',Sel.Value);
  326. sel:=TCSSIdentifierElement(CheckClass('Selector', TCSSIdentifierElement,Bin.Right));
  327. AssertEquals('Sel 2 name','b',Sel.Value);
  328. end;
  329. procedure TTestCSSParser.TestDescendantMixedPrefixedEmptyRule;
  330. var
  331. R : TCSSRuleElement;
  332. sel: TCSSIdentifierElement;
  333. Bin: TCSSBinaryElement;
  334. begin
  335. ParseRule('a .b { }');
  336. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  337. AssertEquals('No rule children',0,R.ChildCount);
  338. AssertEquals('selector count',1,R.SelectorCount);
  339. Bin:=TCSSBinaryElement(CheckClass('Selector', TCSSBinaryElement,R.Selectors[0]));
  340. sel:=TCSSIdentifierElement(CheckClass('Selector', TCSSIdentifierElement,Bin.Left));
  341. AssertEquals('Sel 1 name','a',Sel.Value);
  342. sel:=TCSSClassNameElement(CheckClass('Selector', TCSSClassNameElement,Bin.Right));
  343. AssertEquals('Sel 2 name','b',Sel.Value);
  344. end;
  345. procedure TTestCSSParser.TestAttributePrefixedEmptyRule;
  346. var
  347. R : TCSSRuleElement;
  348. sel: TCSSArrayElement;
  349. id : TCSSIdentifierElement;
  350. bin : TCSSBinaryElement;
  351. List: TCSSListElement;
  352. begin
  353. ParseRule('a[b="c"] { }');
  354. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  355. AssertEquals('No rule children',0,R.ChildCount);
  356. AssertEquals('selector count',1,R.SelectorCount);
  357. List:=TCSSListElement(CheckClass('Selector', TCSSListElement,R.Selectors[0]));
  358. AssertEquals('list selector count',2,List.ChildCount);
  359. Id:=TCSSIdentifierElement(CheckClass('prefix',TCSSIdentifierElement,List[0]));
  360. sel:=TCSSArrayElement(CheckClass('Attribute Selector', TCSSArrayElement,List[1]));
  361. AssertEquals('Prefix name','a',Id.Value);
  362. AssertEquals('Array count',1,Sel.ChildCount);
  363. Bin:=TCSSBinaryElement(CheckClass('Bin',TCSSBinaryElement,sel.children[0]));
  364. AssertEquals('Binary op',boEquals,Bin.Operation);
  365. end;
  366. procedure TTestCSSParser.TestAttributeSquaredEqualRule;
  367. var
  368. R : TCSSRuleElement;
  369. sel: TCSSArrayElement;
  370. bin : TCSSBinaryElement;
  371. Left: TCSSIdentifierElement;
  372. begin
  373. ParseRule('[b^="c"] { }');
  374. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  375. AssertEquals('No rule children',0,R.ChildCount);
  376. AssertEquals('selector count',1,R.SelectorCount);
  377. sel:=TCSSArrayElement(CheckClass('Selector', TCSSArrayElement,R.Selectors[0]));
  378. if Sel.Prefix<>nil then
  379. Fail('no prefix');
  380. AssertEquals('Array count',1,Sel.ChildCount);
  381. Bin:=TCSSBinaryElement(CheckClass('Bin',TCSSBinaryElement,sel.children[0]));
  382. AssertEquals('Binary op',boSquaredEqual,Bin.Operation);
  383. Left:=TCSSIdentifierElement(CheckClass('Bin.Left',TCSSIdentifierElement,Bin.Left));
  384. AssertEquals('left=b','b',Left.Value);
  385. CheckClass('Bin.Right',TCSSStringElement,Bin.Right);
  386. end;
  387. procedure TTestCSSParser.TestAttributePipeEqualRule;
  388. var
  389. R : TCSSRuleElement;
  390. sel: TCSSArrayElement;
  391. bin : TCSSBinaryElement;
  392. Left: TCSSIdentifierElement;
  393. begin
  394. ParseRule('[b|="c"] { }');
  395. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  396. AssertEquals('No rule children',0,R.ChildCount);
  397. AssertEquals('selector count',1,R.SelectorCount);
  398. sel:=TCSSArrayElement(CheckClass('Selector', TCSSArrayElement,R.Selectors[0]));
  399. if Sel.Prefix<>nil then
  400. Fail('no prefix');
  401. AssertEquals('Array count',1,Sel.ChildCount);
  402. Bin:=TCSSBinaryElement(CheckClass('Bin',TCSSBinaryElement,sel.children[0]));
  403. AssertEquals('Binary op',boPipeEqual,Bin.Operation);
  404. Left:=TCSSIdentifierElement(CheckClass('Bin.Left',TCSSIdentifierElement,Bin.Left));
  405. AssertEquals('left=b','b',Left.Value);
  406. CheckClass('Bin.Right',TCSSStringElement,Bin.Right);
  407. end;
  408. procedure TTestCSSParser.TestAttributeStarEqualRule;
  409. var
  410. R : TCSSRuleElement;
  411. sel: TCSSArrayElement;
  412. bin : TCSSBinaryElement;
  413. Left: TCSSIdentifierElement;
  414. begin
  415. ParseRule('[b*="c"] { }');
  416. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  417. AssertEquals('No rule children',0,R.ChildCount);
  418. AssertEquals('selector count',1,R.SelectorCount);
  419. sel:=TCSSArrayElement(CheckClass('Selector', TCSSArrayElement,R.Selectors[0]));
  420. if Sel.Prefix<>nil then
  421. Fail('no prefix');
  422. AssertEquals('Array count',1,Sel.ChildCount);
  423. Bin:=TCSSBinaryElement(CheckClass('Bin',TCSSBinaryElement,sel.children[0]));
  424. AssertEquals('Binary op',boStarEqual,Bin.Operation);
  425. Left:=TCSSIdentifierElement(CheckClass('Bin.Left',TCSSIdentifierElement,Bin.Left));
  426. AssertEquals('left=b','b',Left.Value);
  427. CheckClass('Bin.Right',TCSSStringElement,Bin.Right);
  428. end;
  429. procedure TTestCSSParser.TestAttributeDollarEqualRule;
  430. var
  431. R : TCSSRuleElement;
  432. sel: TCSSArrayElement;
  433. bin : TCSSBinaryElement;
  434. Left: TCSSIdentifierElement;
  435. begin
  436. ParseRule('[b$="c"] { }');
  437. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  438. AssertEquals('No rule children',0,R.ChildCount);
  439. AssertEquals('selector count',1,R.SelectorCount);
  440. sel:=TCSSArrayElement(CheckClass('Selector', TCSSArrayElement,R.Selectors[0]));
  441. if Sel.Prefix<>nil then
  442. Fail('no prefix');
  443. AssertEquals('Array count',1,Sel.ChildCount);
  444. Bin:=TCSSBinaryElement(CheckClass('Bin',TCSSBinaryElement,sel.children[0]));
  445. AssertEquals('Binary op',boDollarEqual,Bin.Operation);
  446. Left:=TCSSIdentifierElement(CheckClass('Bin.Left',TCSSIdentifierElement,Bin.Left));
  447. AssertEquals('left=b','b',Left.Value);
  448. CheckClass('Bin.Right',TCSSStringElement,Bin.Right);
  449. end;
  450. procedure TTestCSSParser.TestAttributeTildeEqualRule;
  451. var
  452. R : TCSSRuleElement;
  453. sel: TCSSArrayElement;
  454. bin : TCSSBinaryElement;
  455. Left: TCSSIdentifierElement;
  456. begin
  457. ParseRule('[b~="c"] { }');
  458. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  459. AssertEquals('No rule children',0,R.ChildCount);
  460. AssertEquals('selector count',1,R.SelectorCount);
  461. sel:=TCSSArrayElement(CheckClass('Selector', TCSSArrayElement,R.Selectors[0]));
  462. if Sel.Prefix<>nil then
  463. Fail('no prefix');
  464. AssertEquals('Array count',1,Sel.ChildCount);
  465. Bin:=TCSSBinaryElement(CheckClass('Bin',TCSSBinaryElement,sel.children[0]));
  466. AssertEquals('Binary op',boTildeEqual,Bin.Operation);
  467. Left:=TCSSIdentifierElement(CheckClass('Bin.Left',TCSSIdentifierElement,Bin.Left));
  468. AssertEquals('left=b','b',Left.Value);
  469. CheckClass('Bin.Right',TCSSStringElement,Bin.Right);
  470. end;
  471. procedure TTestCSSParser.TestPseudoPrefixedEmptyRule;
  472. var
  473. R : TCSSRuleElement;
  474. Sel : TCSSPseudoClassElement;
  475. begin
  476. ParseRule(':a { }');
  477. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  478. AssertEquals('No rule children',0,R.ChildCount);
  479. AssertEquals('selector count',1,R.SelectorCount);
  480. sel:=TCSSPseudoClassElement(CheckClass('Selector', TCSSPseudoClassElement,R.Selectors[0]));
  481. AssertEquals('Pseudo name',':a',sel.Value);
  482. end;
  483. procedure TTestCSSParser.TestPseudoFunctionEmptyRule;
  484. var
  485. R : TCSSRuleElement;
  486. Sel : TCSSCallElement;
  487. Id : TCSSIdentifierElement;
  488. begin
  489. ParseRule(':a(b) { }');
  490. R:=TCSSRuleElement(CheckClass('Rule',TCSSRuleElement,FirstRule));
  491. AssertEquals('No rule children',0,R.ChildCount);
  492. AssertEquals('selector count',1,R.SelectorCount);
  493. sel:=TCSSCallElement(CheckClass('Selector', TCSSCallElement,R.Selectors[0]));
  494. AssertEquals('Pseudo name',':a',sel.Name);
  495. AssertEquals('argument count',1,Sel.ChildCount);
  496. Id:=TCSSIdentifierElement(CheckClass('Argument 1',TCSSIdentifierElement,Sel[0]));
  497. AssertEquals('Argument name','b',id.Name);
  498. end;
  499. procedure TTestCSSParser.TestFuncPrefixedEmptyRule;
  500. var
  501. R : TCSSRuleElement;
  502. List : TCSSListElement;
  503. begin
  504. R:=ParseRule('input:enabled:read-write:-webkit-any(:focus,:hover) { }');
  505. AssertEquals('No rule children',0,R.ChildCount);
  506. AssertEquals('selector count',1,R.SelectorCount);
  507. List:=TCSSListElement(CheckClass('List',TCSSListElement,R.Selectors[0]));
  508. CheckList(List,0,'input');
  509. CheckList(List,1,':enabled');
  510. CheckList(List,2,':read-write');
  511. end;
  512. procedure TTestCSSParser.TestQueryPrefixedEmptyRule;
  513. begin
  514. ParseRule('@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 3) { }');
  515. end;
  516. procedure TTestCSSParser.TestCommaPrefixedEmptyRule;
  517. begin
  518. ParseRule('#facebox .tl,#facebox .tl { }');
  519. end;
  520. procedure TTestCSSParser.TestOneDeclarationIDValue;
  521. var
  522. R : TCSSRuleElement;
  523. D : TCSSDeclarationElement;
  524. Id : TCSSIdentifierElement;
  525. begin
  526. R:=ParseRule('{ a : b; }');
  527. AssertEquals('selector count',0,R.SelectorCount);
  528. D:=CheckDeclaration(R,0,'a');
  529. AssertEquals('Value count', 1, D.ChildCount);
  530. ID:=TCSSIdentifierElement(CheckClass('Value', TCSSIdentifierElement,D.Children[0]));
  531. AssertEquals('Value','b',id.Value);
  532. end;
  533. procedure TTestCSSParser.TestOneDeclarationIDValueAndEmpty;
  534. var
  535. R : TCSSRuleElement;
  536. D : TCSSDeclarationElement;
  537. Id : TCSSIdentifierElement;
  538. begin
  539. R:=ParseRule('{ a : b;; }');
  540. AssertEquals('selector count',0,R.SelectorCount);
  541. D:=CheckDeclaration(R,0,'a');
  542. AssertEquals('Value count', 1, D.ChildCount);
  543. ID:=TCSSIdentifierElement(CheckClass('Value', TCSSIdentifierElement,D.Children[0]));
  544. AssertEquals('Value','b',id.Value);
  545. end;
  546. procedure TTestCSSParser.TestOneDeclarationIntValue;
  547. var
  548. R : TCSSRuleElement;
  549. D : TCSSDeclarationElement;
  550. U : TCSSUnit;
  551. begin
  552. For U in TCSSUnit do
  553. begin
  554. R:=ParseRule('{ a : 1'+CSSUnitNames[U]+'; }');
  555. AssertEquals('selector count',0,R.SelectorCount);
  556. D:=CheckDeclaration(R,0,'a');
  557. AssertEquals('Value count', 1, D.ChildCount);
  558. CheckLiteral('Value for '+CSSUnitNames[U],D.Children[0],1,U);
  559. end;
  560. end;
  561. procedure TTestCSSParser.TestOneDeclarationStringValue;
  562. var
  563. R : TCSSRuleElement;
  564. D : TCSSDeclarationElement;
  565. begin
  566. R:=ParseRule('{ a : "b"; }');
  567. AssertEquals('selector count',0,R.SelectorCount);
  568. D:=CheckDeclaration(R,0,'a');
  569. AssertEquals('Value count', 1, D.ChildCount);
  570. CheckLiteral('Value',D.Children[0],'b');
  571. end;
  572. procedure TTestCSSParser.TestOneDeclarationHashValue;
  573. var
  574. R : TCSSRuleElement;
  575. D : TCSSDeclarationElement;
  576. S : TCSSStringElement;
  577. begin
  578. R:=ParseRule('{ a : #ABABAB; }');
  579. AssertEquals('selector count',0,R.SelectorCount);
  580. D:=CheckDeclaration(R,0,'a');
  581. AssertEquals('Value count', 1, D.ChildCount);
  582. S:=TCSSStringElement(CheckClass('Value', TCSSStringElement,D.Children[0]));
  583. AssertEquals('Value ','#ABABAB',S.Value);
  584. end;
  585. procedure TTestCSSParser.TestOneDeclarationURLValue;
  586. var
  587. R : TCSSRuleElement;
  588. D : TCSSDeclarationElement;
  589. U : TCSSURLElement;
  590. begin
  591. R:=ParseRule('{ a : url("b.c"); }');
  592. AssertEquals('selector count',0,R.SelectorCount);
  593. D:=CheckDeclaration(R,0,'a');
  594. AssertEquals('Value count', 1, D.ChildCount);
  595. U:=TCSSURLElement(CheckClass('Value', TCSSURLElement,D.Children[0]));
  596. AssertEquals('Value ','b.c',U.Value);
  597. end;
  598. procedure TTestCSSParser.TestOneDeclarationFloatValue;
  599. var
  600. R : TCSSRuleElement;
  601. D : TCSSDeclarationElement;
  602. F : TCSSFloatElement;
  603. begin
  604. R:=ParseRule('{ a : -.5em; }');
  605. AssertEquals('selector count',0,R.SelectorCount);
  606. D:=CheckDeclaration(R,0,'a');
  607. AssertEquals('Value count', 1, D.ChildCount);
  608. F:=TCSSFloatElement(CheckClass('Value', TCSSFloatElement,D.Children[0]));
  609. AssertEquals('Value ',-0.5,F.Value);
  610. if F.Units<>cu_em then
  611. Fail('Units expected unit em, but found '+IntToStr(ord(F.Units)));
  612. end;
  613. procedure TTestCSSParser.TestOneDeclarationMultiValue;
  614. var
  615. R : TCSSRuleElement;
  616. D : TCSSDeclarationElement;
  617. L : TCSSListElement;
  618. begin
  619. R:=ParseRule('{ a : 1px 2px 3px 4px; }');
  620. AssertEquals('selector count',0,R.SelectorCount);
  621. D:=CheckDeclaration(R,0,'a');
  622. AssertEquals('Value count', 1, D.ChildCount);
  623. L:=TCSSListElement(CheckClass('List',TCSSListElement,D.Children[0]));
  624. AssertEquals('List element count', 4, L.ChildCount);
  625. CheckLiteral('Value 1 ',L.Children[0],1,cu_px);
  626. CheckLiteral('Value 2 ',L.Children[1],2,cu_px);
  627. CheckLiteral('Value 3 ',L.Children[2],3,cu_px);
  628. CheckLiteral('Value 4 ',L.Children[3],4,cu_px);
  629. end;
  630. procedure TTestCSSParser.TestOneDeclarationMultiListValue;
  631. var
  632. R : TCSSRuleElement;
  633. D : TCSSDeclarationElement;
  634. L : TCSSListElement;
  635. begin
  636. R:=ParseRule('{ a : 1px 2px, 3px 4px; }');
  637. AssertEquals('selector count',0,R.SelectorCount);
  638. D:=CheckDeclaration(R,0,'a');
  639. AssertEquals('Value count', 2, D.ChildCount);
  640. L:=TCSSListElement(CheckClass('List',TCSSListElement,D.Children[0]));
  641. AssertEquals('List element count', 2, L.ChildCount);
  642. CheckLiteral('Value 1 ',L.Children[0],1,cu_px);
  643. CheckLiteral('Value 2 ',L.Children[1],2,cu_px);
  644. L:=TCSSListElement(CheckClass('List',TCSSListElement,D.Children[1]));
  645. AssertEquals('List element count', 2, L.ChildCount);
  646. CheckLiteral('Value 3 ',L.Children[0],3,cu_px);
  647. CheckLiteral('Value 4 ',L.Children[1],4,cu_px);
  648. end;
  649. procedure TTestCSSParser.TestOneDeclarationExprValue;
  650. begin
  651. // Todo
  652. end;
  653. procedure TTestCSSParser.TestOneDeclarationUnicodeRangeValue;
  654. var
  655. R : TCSSRuleElement;
  656. D : TCSSDeclarationElement;
  657. begin
  658. R:=ParseRule('{ unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }');
  659. D:=CheckDeclaration(R,0);
  660. AssertEquals('Count values', 4, D.ChildCount);
  661. CheckClass('Value 0',TCSSUnicodeRangeElement,D.Children[0]);
  662. CheckClass('Value 1',TCSSUnicodeRangeElement,D.Children[1]);
  663. CheckClass('Value 2',TCSSUnicodeRangeElement,D.Children[2]);
  664. CheckClass('Value 3',TCSSUnicodeRangeElement,D.Children[3]);
  665. end;
  666. procedure TTestCSSParser.TestOneDeclarationNoColon;
  667. begin
  668. SkipInvalid:=true;
  669. ParseRule('@a b { 0% { d: e; } }');
  670. end;
  671. procedure TTestCSSParser.TestTwoDeclarationNoColon;
  672. begin
  673. SkipInvalid:=true;
  674. ParseRule('@a b { 0% { d: e; } 100% { f : g; } }');
  675. end;
  676. procedure TTestCSSParser.TestOneEmptyDeclaration;
  677. var
  678. R : TCSSRuleElement;
  679. begin
  680. R:=ParseRule('{ ; }');
  681. AssertEquals('selector count',0,R.SelectorCount);
  682. AssertEquals('declaration count',0,R.ChildCount);
  683. end;
  684. procedure TTestCSSParser.TestImportAtKeyWord;
  685. var
  686. Rule: TCSSRuleElement;
  687. R : TCSSAtRuleElement;
  688. begin
  689. Rule:=ParseRule('@import url("abc.css");');
  690. R:=TCSSAtRuleElement(CheckClass('at',TCSSAtRuleElement,Rule));
  691. AssertEquals('selector count',1,R.SelectorCount);
  692. AssertEquals('declaration count',0,R.ChildCount);
  693. end;
  694. procedure TTestCSSParser.TestMediaPrint;
  695. begin
  696. ParseRule('@media print { *, *:before {} }');
  697. end;
  698. procedure TTestCSSParser.TestSupportsFunction;
  699. begin
  700. ParseRule('@supports ((position: -webkit-sticky) or (position: sticky)) {'+ sLineBreak+
  701. ' .sticky-top { '+ sLineBreak+
  702. ' position: -webkit-sticky; '+ sLineBreak+
  703. ' position: sticky; '+ sLineBreak+
  704. ' top: 0; '+ sLineBreak+
  705. ' z-index: 1020; '+ sLineBreak+
  706. ' } '+ sLineBreak+
  707. '} '
  708. );
  709. end;
  710. procedure TTestCSSParser.TestSkipUnknownFunction;
  711. begin
  712. SkipInvalid:=true;
  713. ParseRule(':-webkit-any(table, thead, tbody, tfoot, tr) > form:-internal-is-html {'+sLineBreak
  714. +' display: none !important;'+sLineBreak
  715. +'}');
  716. end;
  717. { TTestBaseCSSParser }
  718. function TTestBaseCSSParser.GetRule: TCSSRuleElement;
  719. var
  720. L : TCSSCompoundElement;
  721. begin
  722. L:=TCSSCompoundElement(CheckClass('list',TCSSCompoundElement,ParseResult));
  723. AssertTrue('Result has at least 1 child',L.ChildCount>0);
  724. if L.Children[0] is TCSSAtRuleElement then
  725. Result:=TCSSAtRuleElement(CheckClass('First element is rule',TCSSAtRuleElement,L.Children[0]))
  726. else
  727. Result:=TCSSRuleElement(CheckClass('First element is rule',TCSSRuleElement,L.Children[0]));
  728. end;
  729. procedure TTestBaseCSSParser.OnScannerWarn(Sender: TObject; Msg: string);
  730. var
  731. aScanner: TCSSScanner;
  732. begin
  733. aScanner:=FParser.Scanner;
  734. writeln('TTestBaseCSSParser.OnScannerWarn ',aScanner.CurFilename+'('+IntToStr(aScanner.CurRow)+','+IntToStr(aScanner.CurColumn)+') ',Msg);
  735. end;
  736. procedure TTestBaseCSSParser.SetUp;
  737. begin
  738. inherited SetUp;
  739. FParser:=Nil;
  740. FSource:=Nil;
  741. end;
  742. procedure TTestBaseCSSParser.Clear;
  743. begin
  744. if FParseResult<>FToFree then
  745. FreeAndNil(FToFree);
  746. FreeAndNil(FParseResult);
  747. FreeAndNil(FParser);
  748. FReeAndNil(FSource);
  749. end;
  750. procedure TTestBaseCSSParser.TearDown;
  751. begin
  752. Clear;
  753. inherited TearDown;
  754. end;
  755. procedure TTestBaseCSSParser.CreateParser(const ASource: string);
  756. begin
  757. Clear;
  758. FSource:=TStringStream.Create(ASource);
  759. FParser:=TCSSParser.Create(FSource);
  760. if SkipInvalid then
  761. FParser.Scanner.OnWarn:=@OnScannerWarn;
  762. end;
  763. procedure TTestBaseCSSParser.Parse;
  764. begin
  765. FParseResult:=FParser.Parse;
  766. FToFree:=FParseResult;
  767. end;
  768. procedure TTestBaseCSSParser.Parse(const aSource: String);
  769. begin
  770. CreateParser(aSource);
  771. Parse;
  772. end;
  773. function TTestBaseCSSParser.ParseRule(const aSource: String): TCSSRuleElement;
  774. begin
  775. Parse(aSource);
  776. if ParseResult is TCSSRuleElement then
  777. Result:=ParseResult as TCSSRuleElement
  778. else
  779. Result:=FirstRule;
  780. end;
  781. procedure TTestBaseCSSParser.AssertEquals(AMessage : String; AExpected, AActual: TCSSUnit);
  782. Var
  783. S,EN1,EN2 : String;
  784. begin
  785. If (AActual<>AExpected) then
  786. begin
  787. EN1:=GetEnumName(TypeINfo(TCSSUnit),Ord(AExpected));
  788. EN2:=GetEnumName(TypeINfo(TCSSUnit),Ord(AActual));
  789. S:=Format('%s : %s <> %s',[AMessage,EN1,EN2]);
  790. Fail(S);
  791. end;
  792. end;
  793. procedure TTestBaseCSSParser.AssertEquals(AMessage: String; AExpected, AActual: TCSSBinaryOperation);
  794. Var
  795. S,EN1,EN2 : String;
  796. begin
  797. If (AActual<>AExpected) then
  798. begin
  799. EN1:=GetEnumName(TypeINfo(TCSSBinaryOperation),Ord(AExpected));
  800. EN2:=GetEnumName(TypeINfo(TCSSBinaryOperation),Ord(AActual));
  801. S:=Format('%s : %s <> %s',[AMessage,EN1,EN2]);
  802. Fail(S);
  803. end;
  804. end;
  805. function TTestBaseCSSParser.CheckClass(const aMsg: String; aExpectedClass: TCSSElementClass; aActual: TCSSElement): TCSSElement;
  806. begin
  807. AssertNotNull(aMsg+': Not null element',aExpectedClass);
  808. AssertNotNull(aMsg+': Not null class',aActual);
  809. AssertEquals(aMsg,aExpectedClass,aActual.ClassType);
  810. Result:=aActual;
  811. end;
  812. function TTestBaseCSSParser.CheckDeclaration(aRule: TCSSRuleElement; aIndex: Integer): TCSSDeclarationElement;
  813. begin
  814. AssertTrue('Have rule child '+IntToStr(aIndex),aIndex<aRule.ChildCount);
  815. Result:=TCSSDeclarationElement(CheckClass('Decl', TCSSDeclarationElement,aRule.Children[aIndex]));
  816. end;
  817. function TTestBaseCSSParser.CheckDeclaration(aRule: TCSSRuleElement; aIndex: Integer; const AKey: String): TCSSDeclarationElement;
  818. var
  819. ID : TCSSIdentifierElement;
  820. begin
  821. Result:=CheckDeclaration(aRule,aIndex);
  822. AssertEquals('Key count', 1, Result.KeyCount);
  823. ID:=TCSSIdentifierElement(CheckClass('key 0', TCSSIdentifierElement,Result.Keys[0]));
  824. AssertEquals('Key 0 name',aKey,id.Value);
  825. end;
  826. function TTestBaseCSSParser.CheckSelector(aRule: TCSSRuleElement; aIndex: Integer): TCSSElement;
  827. begin
  828. AssertTrue('Have rule selector '+IntToStr(aIndex),aIndex<aRule.SelectorCount);
  829. Result:=aRule.Selectors[aIndex];
  830. AssertNotNull('Have selector non-nil',Result);
  831. end;
  832. function TTestBaseCSSParser.CheckSelector(aRule: TCSSRuleElement; aIndex: Integer; const aName: String): TCSSElement;
  833. begin
  834. Result:=CheckSelector(aRule,aIndex);
  835. if Result is TCSSIdentifierElement then
  836. AssertEquals('Selector '+IntToStr(aIndex)+'name',aName,TCSSIdentifierElement(Result).Name)
  837. else if Result is TCSSStringElement then
  838. AssertEquals('Selector '+IntToStr(aIndex)+'name',aName,TCSSStringElement(Result).Value)
  839. else
  840. Fail('Selector '+IntToStr(aIndex)+' has no known type')
  841. end;
  842. function TTestBaseCSSParser.CheckList(aList: TCSSListElement; aIndex: Integer): TCSSElement;
  843. begin
  844. AssertTrue('Have list index '+IntToStr(aIndex),aIndex<aList.ChildCount);
  845. Result:=aList[aIndex];
  846. AssertNotNull('Have element non-nil',Result);
  847. end;
  848. function TTestBaseCSSParser.CheckList(aList: TCSSListElement; aIndex: Integer; const aName: String): TCSSElement;
  849. begin
  850. Result:=CheckList(aList,aIndex);
  851. if Result is TCSSIdentifierElement then
  852. AssertEquals('List element '+IntToStr(aIndex)+'name',aName,TCSSIdentifierElement(Result).Name)
  853. else if Result is TCSSStringElement then
  854. AssertEquals('List element '+IntToStr(aIndex)+'name',aName,TCSSStringElement(Result).Value)
  855. else
  856. Fail('List element '+IntToStr(aIndex)+' has no known type')
  857. end;
  858. function TTestBaseCSSParser.CheckLiteral(Msg: String; aEl: TCSSelement; aValue: String): TCSSStringElement;
  859. begin
  860. Result:=TCSSStringElement(CheckClass(Msg+': class', TCSSStringElement,aEl));
  861. AssertEquals(Msg+': String Value',aValue,Result.Value);
  862. end;
  863. function TTestBaseCSSParser.CheckLiteral(Msg: String; aEl: TCSSelement; aValue: Integer): TCSSIntegerElement;
  864. begin
  865. Result:=TCSSIntegerElement(CheckClass(Msg+': Class', TCSSIntegerElement,aEl));
  866. AssertEquals(Msg+': Value ',aValue,Result.Value);
  867. end;
  868. function TTestBaseCSSParser.CheckLiteral(Msg: String; aEl: TCSSelement; aValue: Integer; AUnits: TCSSUnit): TCSSIntegerElement;
  869. begin
  870. Result:=CheckLiteral(Msg,aEl,aValue);
  871. AssertEquals('Units',aUnits,Result.Units);
  872. end;
  873. function TTestBaseCSSParser.GetCalArg(aCall: TCSSCallElement; aIndex: Integer): TCSSElement;
  874. begin
  875. AssertNotNull('Have call element',aCall);
  876. AssertTrue('Have argument '+IntToStr(aIndex),aIndex<aCall.ChildCount);
  877. Result:=aCall.Children[0];
  878. AssertNotNull('Have call argument',Result);
  879. end;
  880. initialization
  881. RegisterTests([TTestCSSParser,TTestCSSFilesParser]);
  882. end.