test_rewriteDeclarations.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. /* Any copyright is dedicated to the Public Domain.
  2. http://creativecommons.org/publicdomain/zero/1.0/ */
  3. "use strict";
  4. var Cu = Components.utils;
  5. const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
  6. const {RuleRewriter} = require("devtools/shared/css/parsing-utils");
  7. const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
  8. const TEST_DATA = [
  9. {
  10. desc: "simple set",
  11. input: "p:v;",
  12. instruction: {type: "set", name: "p", value: "N", priority: "",
  13. index: 0},
  14. expected: "p:N;"
  15. },
  16. {
  17. desc: "simple set clearing !important",
  18. input: "p:v !important;",
  19. instruction: {type: "set", name: "p", value: "N", priority: "",
  20. index: 0},
  21. expected: "p:N;"
  22. },
  23. {
  24. desc: "simple set adding !important",
  25. input: "p:v;",
  26. instruction: {type: "set", name: "p", value: "N", priority: "important",
  27. index: 0},
  28. expected: "p:N !important;"
  29. },
  30. {
  31. desc: "simple set between comments",
  32. input: "/*color:red;*/ p:v; /*color:green;*/",
  33. instruction: {type: "set", name: "p", value: "N", priority: "",
  34. index: 1},
  35. expected: "/*color:red;*/ p:N; /*color:green;*/"
  36. },
  37. // The rule view can generate a "set" with a previously unknown
  38. // property index; which should work like "create".
  39. {
  40. desc: "set at unknown index",
  41. input: "a:b; e: f;",
  42. instruction: {type: "set", name: "c", value: "d", priority: "",
  43. index: 2},
  44. expected: "a:b; e: f;c: d;"
  45. },
  46. {
  47. desc: "simple rename",
  48. input: "p:v;",
  49. instruction: {type: "rename", name: "p", newName: "q", index: 0},
  50. expected: "q:v;"
  51. },
  52. // "rename" is passed the name that the user entered, and must do
  53. // any escaping necessary to ensure that this is an identifier.
  54. {
  55. desc: "rename requiring escape",
  56. input: "p:v;",
  57. instruction: {type: "rename", name: "p", newName: "a b", index: 0},
  58. expected: "a\\ b:v;"
  59. },
  60. {
  61. desc: "simple create",
  62. input: "",
  63. instruction: {type: "create", name: "p", value: "v", priority: "important",
  64. index: 0, enabled: true},
  65. expected: "p: v !important;"
  66. },
  67. {
  68. desc: "create between two properties",
  69. input: "a:b; e: f;",
  70. instruction: {type: "create", name: "c", value: "d", priority: "",
  71. index: 1, enabled: true},
  72. expected: "a:b; c: d;e: f;"
  73. },
  74. // "create" is passed the name that the user entered, and must do
  75. // any escaping necessary to ensure that this is an identifier.
  76. {
  77. desc: "create requiring escape",
  78. input: "",
  79. instruction: {type: "create", name: "a b", value: "d", priority: "",
  80. index: 1, enabled: true},
  81. expected: "a\\ b: d;"
  82. },
  83. {
  84. desc: "simple disable",
  85. input: "p:v;",
  86. instruction: {type: "enable", name: "p", value: false, index: 0},
  87. expected: "/*! p:v; */"
  88. },
  89. {
  90. desc: "simple enable",
  91. input: "/* color:v; */",
  92. instruction: {type: "enable", name: "color", value: true, index: 0},
  93. expected: "color:v;"
  94. },
  95. {
  96. desc: "enable with following property in comment",
  97. input: "/* color:red; color: blue; */",
  98. instruction: {type: "enable", name: "color", value: true, index: 0},
  99. expected: "color:red; /* color: blue; */"
  100. },
  101. {
  102. desc: "enable with preceding property in comment",
  103. input: "/* color:red; color: blue; */",
  104. instruction: {type: "enable", name: "color", value: true, index: 1},
  105. expected: "/* color:red; */ color: blue;"
  106. },
  107. {
  108. desc: "simple remove",
  109. input: "a:b;c:d;e:f;",
  110. instruction: {type: "remove", name: "c", index: 1},
  111. expected: "a:b;e:f;"
  112. },
  113. {
  114. desc: "disable with comment ender in string",
  115. input: "content: '*/';",
  116. instruction: {type: "enable", name: "content", value: false, index: 0},
  117. expected: "/*! content: '*\\/'; */"
  118. },
  119. {
  120. desc: "enable with comment ender in string",
  121. input: "/* content: '*\\/'; */",
  122. instruction: {type: "enable", name: "content", value: true, index: 0},
  123. expected: "content: '*/';"
  124. },
  125. {
  126. desc: "enable requiring semicolon insertion",
  127. // Note the lack of a trailing semicolon in the comment.
  128. input: "/* color:red */ color: blue;",
  129. instruction: {type: "enable", name: "color", value: true, index: 0},
  130. expected: "color:red; color: blue;"
  131. },
  132. {
  133. desc: "create requiring semicolon insertion",
  134. // Note the lack of a trailing semicolon.
  135. input: "color: red",
  136. instruction: {type: "create", name: "a", value: "b", priority: "",
  137. index: 1, enabled: true},
  138. expected: "color: red;a: b;"
  139. },
  140. // Newline insertion.
  141. {
  142. desc: "simple newline insertion",
  143. input: "\ncolor: red;\n",
  144. instruction: {type: "create", name: "a", value: "b", priority: "",
  145. index: 1, enabled: true},
  146. expected: "\ncolor: red;\na: b;\n"
  147. },
  148. // Newline insertion.
  149. {
  150. desc: "semicolon insertion before newline",
  151. // Note the lack of a trailing semicolon.
  152. input: "\ncolor: red\n",
  153. instruction: {type: "create", name: "a", value: "b", priority: "",
  154. index: 1, enabled: true},
  155. expected: "\ncolor: red;\na: b;\n"
  156. },
  157. // Newline insertion.
  158. {
  159. desc: "newline and semicolon insertion",
  160. // Note the lack of a trailing semicolon and newline.
  161. input: "\ncolor: red",
  162. instruction: {type: "create", name: "a", value: "b", priority: "",
  163. index: 1, enabled: true},
  164. expected: "\ncolor: red;\na: b;\n"
  165. },
  166. // Newline insertion and indentation.
  167. {
  168. desc: "indentation with create",
  169. input: "\n color: red;\n",
  170. instruction: {type: "create", name: "a", value: "b", priority: "",
  171. index: 1, enabled: true},
  172. expected: "\n color: red;\n a: b;\n"
  173. },
  174. // Newline insertion and indentation.
  175. {
  176. desc: "indentation plus semicolon insertion before newline",
  177. // Note the lack of a trailing semicolon.
  178. input: "\n color: red\n",
  179. instruction: {type: "create", name: "a", value: "b", priority: "",
  180. index: 1, enabled: true},
  181. expected: "\n color: red;\n a: b;\n"
  182. },
  183. {
  184. desc: "indentation inserted before trailing whitespace",
  185. // Note the trailing whitespace. This could come from a rule
  186. // like:
  187. // @supports (mumble) {
  188. // body {
  189. // color: red;
  190. // }
  191. // }
  192. // Here if we create a rule we don't want it to follow
  193. // the indentation of the "}".
  194. input: "\n color: red;\n ",
  195. instruction: {type: "create", name: "a", value: "b", priority: "",
  196. index: 1, enabled: true},
  197. expected: "\n color: red;\n a: b;\n "
  198. },
  199. // Newline insertion and indentation.
  200. {
  201. desc: "indentation comes from preceding comment",
  202. // Note how the comment comes before the declaration.
  203. input: "\n /* comment */ color: red\n",
  204. instruction: {type: "create", name: "a", value: "b", priority: "",
  205. index: 1, enabled: true},
  206. expected: "\n /* comment */ color: red;\n a: b;\n"
  207. },
  208. // Default indentation.
  209. {
  210. desc: "use of default indentation",
  211. input: "\n",
  212. instruction: {type: "create", name: "a", value: "b", priority: "",
  213. index: 0, enabled: true},
  214. expected: "\n\ta: b;\n"
  215. },
  216. // Deletion handles newlines properly.
  217. {
  218. desc: "deletion removes newline",
  219. input: "a:b;\nc:d;\ne:f;",
  220. instruction: {type: "remove", name: "c", index: 1},
  221. expected: "a:b;\ne:f;"
  222. },
  223. // Deletion handles newlines properly.
  224. {
  225. desc: "deletion remove blank line",
  226. input: "\n a:b;\n c:d; \ne:f;",
  227. instruction: {type: "remove", name: "c", index: 1},
  228. expected: "\n a:b;\ne:f;"
  229. },
  230. // Deletion handles newlines properly.
  231. {
  232. desc: "deletion leaves comment",
  233. input: "\n a:b;\n /* something */ c:d; \ne:f;",
  234. instruction: {type: "remove", name: "c", index: 1},
  235. expected: "\n a:b;\n /* something */ \ne:f;"
  236. },
  237. // Deletion handles newlines properly.
  238. {
  239. desc: "deletion leaves previous newline",
  240. input: "\n a:b;\n c:d; \ne:f;",
  241. instruction: {type: "remove", name: "e", index: 2},
  242. expected: "\n a:b;\n c:d; \n"
  243. },
  244. // Deletion handles newlines properly.
  245. {
  246. desc: "deletion removes trailing whitespace",
  247. input: "\n a:b;\n c:d; \n e:f;",
  248. instruction: {type: "remove", name: "e", index: 2},
  249. expected: "\n a:b;\n c:d; \n"
  250. },
  251. // Deletion handles newlines properly.
  252. {
  253. desc: "deletion preserves indentation",
  254. input: " a:b;\n c:d; \n e:f;",
  255. instruction: {type: "remove", name: "a", index: 0},
  256. expected: " c:d; \n e:f;"
  257. },
  258. // Termination insertion corner case.
  259. {
  260. desc: "enable single quote termination",
  261. input: "/* content: 'hi */ color: red;",
  262. instruction: {type: "enable", name: "content", value: true, index: 0},
  263. expected: "content: 'hi'; color: red;",
  264. changed: {0: "'hi'"}
  265. },
  266. // Termination insertion corner case.
  267. {
  268. desc: "create single quote termination",
  269. input: "content: 'hi",
  270. instruction: {type: "create", name: "color", value: "red", priority: "",
  271. index: 1, enabled: true},
  272. expected: "content: 'hi';color: red;",
  273. changed: {0: "'hi'"}
  274. },
  275. // Termination insertion corner case.
  276. {
  277. desc: "enable double quote termination",
  278. input: "/* content: \"hi */ color: red;",
  279. instruction: {type: "enable", name: "content", value: true, index: 0},
  280. expected: "content: \"hi\"; color: red;",
  281. changed: {0: "\"hi\""}
  282. },
  283. // Termination insertion corner case.
  284. {
  285. desc: "create double quote termination",
  286. input: "content: \"hi",
  287. instruction: {type: "create", name: "color", value: "red", priority: "",
  288. index: 1, enabled: true},
  289. expected: "content: \"hi\";color: red;",
  290. changed: {0: "\"hi\""}
  291. },
  292. // Termination insertion corner case.
  293. {
  294. desc: "enable url termination",
  295. input: "/* background-image: url(something.jpg */ color: red;",
  296. instruction: {type: "enable", name: "background-image", value: true,
  297. index: 0},
  298. expected: "background-image: url(something.jpg); color: red;",
  299. changed: {0: "url(something.jpg)"}
  300. },
  301. // Termination insertion corner case.
  302. {
  303. desc: "create url termination",
  304. input: "background-image: url(something.jpg",
  305. instruction: {type: "create", name: "color", value: "red", priority: "",
  306. index: 1, enabled: true},
  307. expected: "background-image: url(something.jpg);color: red;",
  308. changed: {0: "url(something.jpg)"}
  309. },
  310. // Termination insertion corner case.
  311. {
  312. desc: "enable url single quote termination",
  313. input: "/* background-image: url('something.jpg */ color: red;",
  314. instruction: {type: "enable", name: "background-image", value: true,
  315. index: 0},
  316. expected: "background-image: url('something.jpg'); color: red;",
  317. changed: {0: "url('something.jpg')"}
  318. },
  319. // Termination insertion corner case.
  320. {
  321. desc: "create url single quote termination",
  322. input: "background-image: url('something.jpg",
  323. instruction: {type: "create", name: "color", value: "red", priority: "",
  324. index: 1, enabled: true},
  325. expected: "background-image: url('something.jpg');color: red;",
  326. changed: {0: "url('something.jpg')"}
  327. },
  328. // Termination insertion corner case.
  329. {
  330. desc: "create url double quote termination",
  331. input: "/* background-image: url(\"something.jpg */ color: red;",
  332. instruction: {type: "enable", name: "background-image", value: true,
  333. index: 0},
  334. expected: "background-image: url(\"something.jpg\"); color: red;",
  335. changed: {0: "url(\"something.jpg\")"}
  336. },
  337. // Termination insertion corner case.
  338. {
  339. desc: "enable url double quote termination",
  340. input: "background-image: url(\"something.jpg",
  341. instruction: {type: "create", name: "color", value: "red", priority: "",
  342. index: 1, enabled: true},
  343. expected: "background-image: url(\"something.jpg\");color: red;",
  344. changed: {0: "url(\"something.jpg\")"}
  345. },
  346. // Termination insertion corner case.
  347. {
  348. desc: "create backslash termination",
  349. input: "something: \\",
  350. instruction: {type: "create", name: "color", value: "red", priority: "",
  351. index: 1, enabled: true},
  352. expected: "something: \\\\;color: red;",
  353. // The lexer rewrites the token before we see it. However this is
  354. // so obscure as to be inconsequential.
  355. changed: {0: "\uFFFD\\"}
  356. },
  357. // Termination insertion corner case.
  358. {
  359. desc: "enable backslash single quote termination",
  360. input: "something: '\\",
  361. instruction: {type: "create", name: "color", value: "red", priority: "",
  362. index: 1, enabled: true},
  363. expected: "something: '\\\\';color: red;",
  364. changed: {0: "'\\\\'"}
  365. },
  366. {
  367. desc: "enable backslash double quote termination",
  368. input: "something: \"\\",
  369. instruction: {type: "create", name: "color", value: "red", priority: "",
  370. index: 1, enabled: true},
  371. expected: "something: \"\\\\\";color: red;",
  372. changed: {0: "\"\\\\\""}
  373. },
  374. // Termination insertion corner case.
  375. {
  376. desc: "enable comment termination",
  377. input: "something: blah /* comment ",
  378. instruction: {type: "create", name: "color", value: "red", priority: "",
  379. index: 1, enabled: true},
  380. expected: "something: blah /* comment*/; color: red;"
  381. },
  382. // Rewrite a "heuristic override" comment.
  383. {
  384. desc: "enable with heuristic override comment",
  385. input: "/*! walrus: zebra; */",
  386. instruction: {type: "enable", name: "walrus", value: true, index: 0},
  387. expected: "walrus: zebra;"
  388. },
  389. // Sanitize a bad value.
  390. {
  391. desc: "create sanitize unpaired brace",
  392. input: "",
  393. instruction: {type: "create", name: "p", value: "}", priority: "",
  394. index: 0, enabled: true},
  395. expected: "p: \\};",
  396. changed: {0: "\\}"}
  397. },
  398. // Sanitize a bad value.
  399. {
  400. desc: "set sanitize unpaired brace",
  401. input: "walrus: zebra;",
  402. instruction: {type: "set", name: "walrus", value: "{{}}}", priority: "",
  403. index: 0},
  404. expected: "walrus: {{}}\\};",
  405. changed: {0: "{{}}\\}"}
  406. },
  407. // Sanitize a bad value.
  408. {
  409. desc: "enable sanitize unpaired brace",
  410. input: "/*! walrus: }*/",
  411. instruction: {type: "enable", name: "walrus", value: true, index: 0},
  412. expected: "walrus: \\};",
  413. changed: {0: "\\}"}
  414. },
  415. // Creating a new declaration does not require an attempt to
  416. // terminate a previous commented declaration.
  417. {
  418. desc: "disabled declaration does not need semicolon insertion",
  419. input: "/*! no: semicolon */\n",
  420. instruction: {type: "create", name: "walrus", value: "zebra", priority: "",
  421. index: 1, enabled: true},
  422. expected: "/*! no: semicolon */\nwalrus: zebra;\n",
  423. changed: {}
  424. },
  425. {
  426. desc: "create commented-out property",
  427. input: "p: v",
  428. instruction: {type: "create", name: "shoveler", value: "duck", priority: "",
  429. index: 1, enabled: false},
  430. expected: "p: v;/*! shoveler: duck; */",
  431. },
  432. {
  433. desc: "disabled create with comment ender in string",
  434. input: "",
  435. instruction: {type: "create", name: "content", value: "'*/'", priority: "",
  436. index: 0, enabled: false},
  437. expected: "/*! content: '*\\/'; */"
  438. },
  439. {
  440. desc: "delete disabled property",
  441. input: "\n a:b;\n /* color:#f0c; */\n e:f;",
  442. instruction: {type: "remove", name: "color", index: 1},
  443. expected: "\n a:b;\n e:f;",
  444. },
  445. {
  446. desc: "delete heuristic-disabled property",
  447. input: "\n a:b;\n /*! c:d; */\n e:f;",
  448. instruction: {type: "remove", name: "c", index: 1},
  449. expected: "\n a:b;\n e:f;",
  450. },
  451. {
  452. desc: "delete disabled property leaving other disabled property",
  453. input: "\n a:b;\n /* color:#f0c; background-color: seagreen; */\n e:f;",
  454. instruction: {type: "remove", name: "color", index: 1},
  455. expected: "\n a:b;\n /* background-color: seagreen; */\n e:f;",
  456. },
  457. ];
  458. function rewriteDeclarations(inputString, instruction, defaultIndentation) {
  459. let rewriter = new RuleRewriter(isCssPropertyKnown, null, inputString);
  460. rewriter.defaultIndentation = defaultIndentation;
  461. switch (instruction.type) {
  462. case "rename":
  463. rewriter.renameProperty(instruction.index, instruction.name,
  464. instruction.newName);
  465. break;
  466. case "enable":
  467. rewriter.setPropertyEnabled(instruction.index, instruction.name,
  468. instruction.value);
  469. break;
  470. case "create":
  471. rewriter.createProperty(instruction.index, instruction.name,
  472. instruction.value, instruction.priority,
  473. instruction.enabled);
  474. break;
  475. case "set":
  476. rewriter.setProperty(instruction.index, instruction.name,
  477. instruction.value, instruction.priority);
  478. break;
  479. case "remove":
  480. rewriter.removeProperty(instruction.index, instruction.name);
  481. break;
  482. default:
  483. throw new Error("unrecognized instruction");
  484. }
  485. return rewriter.getResult();
  486. }
  487. function run_test() {
  488. for (let test of TEST_DATA) {
  489. let {changed, text} = rewriteDeclarations(test.input, test.instruction,
  490. "\t");
  491. equal(text, test.expected, "output for " + test.desc);
  492. let expectChanged;
  493. if ("changed" in test) {
  494. expectChanged = test.changed;
  495. } else {
  496. expectChanged = {};
  497. }
  498. deepEqual(changed, expectChanged, "changed result for " + test.desc);
  499. }
  500. }