test_parseDeclarations.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* Any copyright is dedicated to the Public Domain.
  3. http://creativecommons.org/publicdomain/zero/1.0/ */
  4. "use strict";
  5. const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
  6. const {parseDeclarations, _parseCommentDeclarations} = require("devtools/shared/css/parsing-utils");
  7. const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
  8. const TEST_DATA = [
  9. // Simple test
  10. {
  11. input: "p:v;",
  12. expected: [{name: "p", value: "v", priority: "", offsets: [0, 4]}]
  13. },
  14. // Simple test
  15. {
  16. input: "this:is;a:test;",
  17. expected: [
  18. {name: "this", value: "is", priority: "", offsets: [0, 8]},
  19. {name: "a", value: "test", priority: "", offsets: [8, 15]}
  20. ]
  21. },
  22. // Test a single declaration with semi-colon
  23. {
  24. input: "name:value;",
  25. expected: [{name: "name", value: "value", priority: "", offsets: [0, 11]}]
  26. },
  27. // Test a single declaration without semi-colon
  28. {
  29. input: "name:value",
  30. expected: [{name: "name", value: "value", priority: "", offsets: [0, 10]}]
  31. },
  32. // Test multiple declarations separated by whitespaces and carriage
  33. // returns and tabs
  34. {
  35. input: "p1 : v1 ; \t\t \n p2:v2; \n\n\n\n\t p3 : v3;",
  36. expected: [
  37. {name: "p1", value: "v1", priority: "", offsets: [0, 9]},
  38. {name: "p2", value: "v2", priority: "", offsets: [16, 22]},
  39. {name: "p3", value: "v3", priority: "", offsets: [32, 45]},
  40. ]
  41. },
  42. // Test simple priority
  43. {
  44. input: "p1: v1; p2: v2 !important;",
  45. expected: [
  46. {name: "p1", value: "v1", priority: "", offsets: [0, 7]},
  47. {name: "p2", value: "v2", priority: "important", offsets: [8, 26]}
  48. ]
  49. },
  50. // Test simple priority
  51. {
  52. input: "p1: v1 !important; p2: v2",
  53. expected: [
  54. {name: "p1", value: "v1", priority: "important", offsets: [0, 18]},
  55. {name: "p2", value: "v2", priority: "", offsets: [19, 25]}
  56. ]
  57. },
  58. // Test simple priority
  59. {
  60. input: "p1: v1 ! important; p2: v2 ! important;",
  61. expected: [
  62. {name: "p1", value: "v1", priority: "important", offsets: [0, 20]},
  63. {name: "p2", value: "v2", priority: "important", offsets: [21, 40]}
  64. ]
  65. },
  66. // Test invalid priority
  67. {
  68. input: "p1: v1 important;",
  69. expected: [
  70. {name: "p1", value: "v1 important", priority: "", offsets: [0, 17]}
  71. ]
  72. },
  73. // Test various types of background-image urls
  74. {
  75. input: "background-image: url(../../relative/image.png)",
  76. expected: [{
  77. name: "background-image",
  78. value: "url(../../relative/image.png)",
  79. priority: "",
  80. offsets: [0, 47]
  81. }]
  82. },
  83. {
  84. input: "background-image: url(http://site.com/test.png)",
  85. expected: [{
  86. name: "background-image",
  87. value: "url(http://site.com/test.png)",
  88. priority: "",
  89. offsets: [0, 47]
  90. }]
  91. },
  92. {
  93. input: "background-image: url(wow.gif)",
  94. expected: [{
  95. name: "background-image",
  96. value: "url(wow.gif)",
  97. priority: "",
  98. offsets: [0, 30]
  99. }]
  100. },
  101. // Test that urls with :;{} characters in them are parsed correctly
  102. {
  103. input: "background: red url(\"http://site.com/image{}:;.png?id=4#wat\") "
  104. + "repeat top right",
  105. expected: [{
  106. name: "background",
  107. value: "red url(\"http://site.com/image{}:;.png?id=4#wat\") " +
  108. "repeat top right",
  109. priority: "",
  110. offsets: [0, 78]
  111. }]
  112. },
  113. // Test that an empty string results in an empty array
  114. {input: "", expected: []},
  115. // Test that a string comprised only of whitespaces results in an empty array
  116. {input: " \n \n \n \n \t \t\t\t ", expected: []},
  117. // Test that a null input throws an exception
  118. {input: null, throws: true},
  119. // Test that a undefined input throws an exception
  120. {input: undefined, throws: true},
  121. // Test that :;{} characters in quoted content are not parsed as multiple
  122. // declarations
  123. {
  124. input: "content: \";color:red;}selector{color:yellow;\"",
  125. expected: [{
  126. name: "content",
  127. value: "\";color:red;}selector{color:yellow;\"",
  128. priority: "",
  129. offsets: [0, 45]
  130. }]
  131. },
  132. // Test that rules aren't parsed, just declarations. So { and } found after a
  133. // property name should be part of the property name, same for values.
  134. {
  135. input: "body {color:red;} p {color: blue;}",
  136. expected: [
  137. {name: "body {color", value: "red", priority: "", offsets: [0, 16]},
  138. {name: "} p {color", value: "blue", priority: "", offsets: [16, 33]},
  139. {name: "}", value: "", priority: "", offsets: [33, 34]}
  140. ]
  141. },
  142. // Test unbalanced : and ;
  143. {
  144. input: "color :red : font : arial;",
  145. expected: [
  146. {name: "color", value: "red : font : arial", priority: "",
  147. offsets: [0, 26]}
  148. ]
  149. },
  150. {
  151. input: "background: red;;;;;",
  152. expected: [{name: "background", value: "red", priority: "",
  153. offsets: [0, 16]}]
  154. },
  155. {
  156. input: "background:;",
  157. expected: [{name: "background", value: "", priority: "",
  158. offsets: [0, 12]}]
  159. },
  160. {input: ";;;;;", expected: []},
  161. {input: ":;:;", expected: []},
  162. // Test name only
  163. {input: "color", expected: [
  164. {name: "color", value: "", priority: "", offsets: [0, 5]}
  165. ]},
  166. // Test trailing name without :
  167. {input: "color:blue;font", expected: [
  168. {name: "color", value: "blue", priority: "", offsets: [0, 11]},
  169. {name: "font", value: "", priority: "", offsets: [11, 15]}
  170. ]},
  171. // Test trailing name with :
  172. {input: "color:blue;font:", expected: [
  173. {name: "color", value: "blue", priority: "", offsets: [0, 11]},
  174. {name: "font", value: "", priority: "", offsets: [11, 16]}
  175. ]},
  176. // Test leading value
  177. {input: "Arial;color:blue;", expected: [
  178. {name: "", value: "Arial", priority: "", offsets: [0, 6]},
  179. {name: "color", value: "blue", priority: "", offsets: [6, 17]}
  180. ]},
  181. // Test hex colors
  182. {
  183. input: "color: #333",
  184. expected: [{name: "color", value: "#333", priority: "", offsets: [0, 11]}]
  185. },
  186. {
  187. input: "color: #456789",
  188. expected: [{name: "color", value: "#456789", priority: "",
  189. offsets: [0, 14]}]
  190. },
  191. {
  192. input: "wat: #XYZ",
  193. expected: [{name: "wat", value: "#XYZ", priority: "", offsets: [0, 9]}]
  194. },
  195. // Test string/url quotes escaping
  196. {
  197. input: "content: \"this is a 'string'\"",
  198. expected: [{name: "content", value: "\"this is a 'string'\"", priority: "",
  199. offsets: [0, 29]}]
  200. },
  201. {
  202. input: 'content: "this is a \\"string\\""',
  203. expected: [{
  204. name: "content",
  205. value: '"this is a \\"string\\""',
  206. priority: "",
  207. offsets: [0, 31]}]
  208. },
  209. {
  210. input: "content: 'this is a \"string\"'",
  211. expected: [{
  212. name: "content",
  213. value: '\'this is a "string"\'',
  214. priority: "",
  215. offsets: [0, 29]
  216. }]
  217. },
  218. {
  219. input: "content: 'this is a \\'string\\''",
  220. expected: [{
  221. name: "content",
  222. value: "'this is a \\'string\\''",
  223. priority: "",
  224. offsets: [0, 31],
  225. }]
  226. },
  227. {
  228. input: "content: 'this \\' is a \" really strange string'",
  229. expected: [{
  230. name: "content",
  231. value: "'this \\' is a \" really strange string'",
  232. priority: "",
  233. offsets: [0, 47]
  234. }]
  235. },
  236. {
  237. input: "content: \"a not s\\ o very long title\"",
  238. expected: [{
  239. name: "content",
  240. value: '"a not s\\ o very long title"',
  241. priority: "",
  242. offsets: [0, 46]
  243. }]
  244. },
  245. // Test calc with nested parentheses
  246. {
  247. input: "width: calc((100% - 3em) / 2)",
  248. expected: [{name: "width", value: "calc((100% - 3em) / 2)", priority: "",
  249. offsets: [0, 29]}]
  250. },
  251. // Simple embedded comment test.
  252. {
  253. parseComments: true,
  254. input: "width: 5; /* background: green; */ background: red;",
  255. expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
  256. {name: "background", value: "green", priority: "",
  257. offsets: [13, 31], commentOffsets: [10, 34]},
  258. {name: "background", value: "red", priority: "",
  259. offsets: [35, 51]}]
  260. },
  261. // Embedded comment where the parsing heuristic fails.
  262. {
  263. parseComments: true,
  264. input: "width: 5; /* background something: green; */ background: red;",
  265. expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
  266. {name: "background", value: "red", priority: "",
  267. offsets: [45, 61]}]
  268. },
  269. // Embedded comment where the parsing heuristic is a bit funny.
  270. {
  271. parseComments: true,
  272. input: "width: 5; /* background: */ background: red;",
  273. expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
  274. {name: "background", value: "", priority: "",
  275. offsets: [13, 24], commentOffsets: [10, 27]},
  276. {name: "background", value: "red", priority: "",
  277. offsets: [28, 44]}]
  278. },
  279. // Another case where the parsing heuristic says not to bother; note
  280. // that there is no ";" in the comment.
  281. {
  282. parseComments: true,
  283. input: "width: 5; /* background: yellow */ background: red;",
  284. expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
  285. {name: "background", value: "yellow", priority: "",
  286. offsets: [13, 31], commentOffsets: [10, 34]},
  287. {name: "background", value: "red", priority: "",
  288. offsets: [35, 51]}]
  289. },
  290. // Parsing a comment should yield text that has been unescaped, and
  291. // the offsets should refer to the original text.
  292. {
  293. parseComments: true,
  294. input: "/* content: '*\\/'; */",
  295. expected: [{name: "content", value: "'*/'", priority: "",
  296. offsets: [3, 18], commentOffsets: [0, 21]}]
  297. },
  298. // Parsing a comment should yield text that has been unescaped, and
  299. // the offsets should refer to the original text. This variant
  300. // tests the no-semicolon path.
  301. {
  302. parseComments: true,
  303. input: "/* content: '*\\/' */",
  304. expected: [{name: "content", value: "'*/'", priority: "",
  305. offsets: [3, 17], commentOffsets: [0, 20]}]
  306. },
  307. // A comment-in-a-comment should yield the correct offsets.
  308. {
  309. parseComments: true,
  310. input: "/* color: /\\* comment *\\/ red; */",
  311. expected: [{name: "color", value: "red", priority: "",
  312. offsets: [3, 30], commentOffsets: [0, 33]}]
  313. },
  314. // HTML comments are ignored.
  315. {
  316. parseComments: true,
  317. input: "<!-- color: red; --> color: blue;",
  318. expected: [{name: "color", value: "red", priority: "",
  319. offsets: [5, 16]},
  320. {name: "color", value: "blue", priority: "",
  321. offsets: [21, 33]}]
  322. },
  323. // Don't error on an empty comment.
  324. {
  325. parseComments: true,
  326. input: "/**/",
  327. expected: []
  328. },
  329. // Parsing our special comments skips the name-check heuristic.
  330. {
  331. parseComments: true,
  332. input: "/*! walrus: zebra; */",
  333. expected: [{name: "walrus", value: "zebra", priority: "",
  334. offsets: [4, 18], commentOffsets: [0, 21]}]
  335. },
  336. // Regression test for bug 1287620.
  337. {
  338. input: "color: blue \\9 no\\_need",
  339. expected: [{name: "color", value: "blue \\9 no_need", priority: "", offsets: [0, 23]}]
  340. },
  341. // Regression test for bug 1297890 - don't paste tokens.
  342. {
  343. parseComments: true,
  344. input: "stroke-dasharray: 1/*ThisIsAComment*/2;",
  345. expected: [{name: "stroke-dasharray", value: "1 2", priority: "", offsets: [0, 39]}]
  346. },
  347. ];
  348. function run_test() {
  349. run_basic_tests();
  350. run_comment_tests();
  351. }
  352. // Test parseDeclarations.
  353. function run_basic_tests() {
  354. for (let test of TEST_DATA) {
  355. do_print("Test input string " + test.input);
  356. let output;
  357. try {
  358. output = parseDeclarations(isCssPropertyKnown, test.input,
  359. test.parseComments);
  360. } catch (e) {
  361. do_print("parseDeclarations threw an exception with the given input " +
  362. "string");
  363. if (test.throws) {
  364. do_print("Exception expected");
  365. do_check_true(true);
  366. } else {
  367. do_print("Exception unexpected\n" + e);
  368. do_check_true(false);
  369. }
  370. }
  371. if (output) {
  372. assertOutput(output, test.expected);
  373. }
  374. }
  375. }
  376. const COMMENT_DATA = [
  377. {
  378. input: "content: 'hi",
  379. expected: [{name: "content", value: "'hi", priority: "", terminator: "';",
  380. offsets: [2, 14], colonOffsets: [9, 11],
  381. commentOffsets: [0, 16]}],
  382. },
  383. {
  384. input: "text that once confounded the parser;",
  385. expected: []
  386. },
  387. ];
  388. // Test parseCommentDeclarations.
  389. function run_comment_tests() {
  390. for (let test of COMMENT_DATA) {
  391. do_print("Test input string " + test.input);
  392. let output = _parseCommentDeclarations(isCssPropertyKnown, test.input, 0,
  393. test.input.length + 4);
  394. deepEqual(output, test.expected);
  395. }
  396. }
  397. function assertOutput(actual, expected) {
  398. if (actual.length === expected.length) {
  399. for (let i = 0; i < expected.length; i++) {
  400. do_check_true(!!actual[i]);
  401. do_print("Check that the output item has the expected name, " +
  402. "value and priority");
  403. do_check_eq(expected[i].name, actual[i].name);
  404. do_check_eq(expected[i].value, actual[i].value);
  405. do_check_eq(expected[i].priority, actual[i].priority);
  406. deepEqual(expected[i].offsets, actual[i].offsets);
  407. if ("commentOffsets" in expected[i]) {
  408. deepEqual(expected[i].commentOffsets, actual[i].commentOffsets);
  409. }
  410. }
  411. } else {
  412. for (let prop of actual) {
  413. do_print("Actual output contained: {name: " + prop.name + ", value: " +
  414. prop.value + ", priority: " + prop.priority + "}");
  415. }
  416. do_check_eq(actual.length, expected.length);
  417. }
  418. }