test_rewriteDeclarations.js (22219B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const RuleRewriter = require("resource://devtools/client/fronts/inspector/rule-rewriter.js"); 7 const { 8 isCssPropertyKnown, 9 } = require("resource://devtools/server/actors/css-properties.js"); 10 11 const TEST_DATA = [ 12 { 13 desc: "simple set", 14 input: "p:v;", 15 instruction: { type: "set", name: "p", value: "N", priority: "", index: 0 }, 16 expected: "p:N;", 17 }, 18 { 19 desc: "set in rule with nested rule", 20 input: "a:b; span { e:f; }", 21 instruction: { type: "set", name: "a", value: "N", priority: "", index: 0 }, 22 expected: "a:N; span { e:f; }", 23 }, 24 { 25 desc: "simple set clearing !important", 26 input: "p:v !important;", 27 instruction: { type: "set", name: "p", value: "N", priority: "", index: 0 }, 28 expected: "p:N;", 29 }, 30 { 31 desc: "simple set adding !important", 32 input: "p:v;", 33 instruction: { 34 type: "set", 35 name: "p", 36 value: "N", 37 priority: "important", 38 index: 0, 39 }, 40 expected: "p:N !important;", 41 }, 42 { 43 desc: "simple set between comments", 44 input: "/*color:red;*/ p:v; /*color:green;*/", 45 instruction: { type: "set", name: "p", value: "N", priority: "", index: 1 }, 46 expected: "/*color:red;*/ p:N; /*color:green;*/", 47 }, 48 // The rule view can generate a "set" with a previously unknown 49 // property index; which should work like "create". 50 { 51 desc: "set at unknown index", 52 input: "a:b; e: f;", 53 instruction: { type: "set", name: "c", value: "d", priority: "", index: 2 }, 54 expected: "a:b; e: f;c: d;", 55 }, 56 { 57 desc: "simple rename", 58 input: "p:v;", 59 instruction: { type: "rename", name: "p", newName: "q", index: 0 }, 60 expected: "q:v;", 61 }, 62 { 63 desc: "rename in rule with nested rule", 64 input: "a:b; span { e:f; }", 65 instruction: { type: "rename", name: "a", newName: "q", index: 0 }, 66 expected: "q:b; span { e:f; }", 67 }, 68 // "rename" is passed the name that the user entered, and must do 69 // any escaping necessary to ensure that this is an identifier. 70 { 71 desc: "rename requiring escape", 72 input: "p:v;", 73 instruction: { type: "rename", name: "p", newName: "a b", index: 0 }, 74 expected: "a\\ b:v;", 75 }, 76 { 77 desc: "simple create", 78 input: "", 79 instruction: { 80 type: "create", 81 name: "p", 82 value: "v", 83 priority: "important", 84 index: 0, 85 enabled: true, 86 }, 87 expected: "p: v !important;", 88 }, 89 { 90 desc: "create between two properties", 91 input: "a:b; e: f;", 92 instruction: { 93 type: "create", 94 name: "c", 95 value: "d", 96 priority: "", 97 index: 1, 98 enabled: true, 99 }, 100 expected: "a:b; c: d;e: f;", 101 }, 102 { 103 desc: "create at the end of rule with nested rule", 104 input: "a:b; span { e:f; }", 105 instruction: { 106 type: "create", 107 name: "c", 108 value: "d", 109 priority: "", 110 index: 1, 111 enabled: true, 112 }, 113 expected: "a:b; c: d;span { e:f; }", 114 }, 115 { 116 desc: "create at the end of rule with nested rule and line breaks", 117 input: ` 118 position: absolute; 119 &.class {}`, 120 instruction: { 121 type: "create", 122 name: "color", 123 value: "red", 124 priority: "", 125 index: 1, 126 enabled: true, 127 }, 128 expected: ` 129 position: absolute; 130 color: red; 131 &.class {}`, 132 }, 133 { 134 desc: "create at the end of rule with CSSNestedDeclarations", 135 input: "a:b; span { e:f; } g: h;", 136 instruction: { 137 type: "create", 138 name: "c", 139 value: "d", 140 priority: "", 141 index: 1, 142 enabled: true, 143 }, 144 expected: "a:b; c: d;span { e:f; } g: h;", 145 }, 146 // "create" is passed the name that the user entered, and must do 147 // any escaping necessary to ensure that this is an identifier. 148 { 149 desc: "create requiring escape", 150 input: "", 151 instruction: { 152 type: "create", 153 name: "a b", 154 value: "d", 155 priority: "", 156 index: 1, 157 enabled: true, 158 }, 159 expected: "a\\ b: d;", 160 }, 161 { 162 desc: "simple disable", 163 input: "p:v;", 164 instruction: { type: "enable", name: "p", value: false, index: 0 }, 165 expected: "/*! p:v; */", 166 }, 167 { 168 desc: "simple enable", 169 input: "/* color:v; */", 170 instruction: { type: "enable", name: "color", value: true, index: 0 }, 171 expected: "color:v;", 172 }, 173 { 174 desc: "enable with following property in comment", 175 input: "/* color:red; color: blue; */", 176 instruction: { type: "enable", name: "color", value: true, index: 0 }, 177 expected: "color:red; /* color: blue; */", 178 }, 179 { 180 desc: "enable with preceding property in comment", 181 input: "/* color:red; color: blue; */", 182 instruction: { type: "enable", name: "color", value: true, index: 1 }, 183 expected: "/* color:red; */ color: blue;", 184 }, 185 { 186 desc: "disable on rule with nested rule", 187 input: "a:b; span { c:d; }", 188 instruction: { type: "enable", name: "a", value: false, index: 0 }, 189 expected: "/*! a:b; */ span { c:d; }", 190 }, 191 { 192 desc: "enable on rule with nested rule", 193 input: "/*! a:b; */ span { c:d; }", 194 instruction: { type: "enable", name: "a", value: true, index: 0 }, 195 expected: "a:b; span { c:d; }", 196 }, 197 { 198 desc: "simple remove", 199 input: "a:b;c:d;e:f;", 200 instruction: { type: "remove", name: "c", index: 1 }, 201 expected: "a:b;e:f;", 202 }, 203 { 204 desc: "remove on rule with nested rule", 205 input: "a:b;c:d;span { e:f; }", 206 instruction: { type: "remove", name: "c", index: 1 }, 207 expected: "a:b;span { e:f; }", 208 }, 209 { 210 desc: "disable with comment ender in string", 211 input: "content: '*/';", 212 instruction: { type: "enable", name: "content", value: false, index: 0 }, 213 expected: "/*! content: '*\\/'; */", 214 }, 215 { 216 desc: "enable with comment ender in string", 217 input: "/* content: '*\\/'; */", 218 instruction: { type: "enable", name: "content", value: true, index: 0 }, 219 expected: "content: '*/';", 220 }, 221 { 222 desc: "enable requiring semicolon insertion", 223 // Note the lack of a trailing semicolon in the comment. 224 input: "/* color:red */ color: blue;", 225 instruction: { type: "enable", name: "color", value: true, index: 0 }, 226 expected: "color:red; color: blue;", 227 }, 228 { 229 desc: "create requiring semicolon insertion", 230 // Note the lack of a trailing semicolon. 231 input: "color: red", 232 instruction: { 233 type: "create", 234 name: "a", 235 value: "b", 236 priority: "", 237 index: 1, 238 enabled: true, 239 }, 240 expected: "color: red;a: b;", 241 }, 242 243 // Newline insertion. 244 { 245 desc: "simple newline insertion", 246 input: "\ncolor: red;\n", 247 instruction: { 248 type: "create", 249 name: "a", 250 value: "b", 251 priority: "", 252 index: 1, 253 enabled: true, 254 }, 255 expected: "\ncolor: red;\na: b;\n", 256 }, 257 // Newline insertion. 258 { 259 desc: "semicolon insertion before newline", 260 // Note the lack of a trailing semicolon. 261 input: "\ncolor: red\n", 262 instruction: { 263 type: "create", 264 name: "a", 265 value: "b", 266 priority: "", 267 index: 1, 268 enabled: true, 269 }, 270 expected: "\ncolor: red;\na: b;\n", 271 }, 272 // Newline insertion. 273 { 274 desc: "newline and semicolon insertion", 275 // Note the lack of a trailing semicolon and newline. 276 input: "\ncolor: red", 277 instruction: { 278 type: "create", 279 name: "a", 280 value: "b", 281 priority: "", 282 index: 1, 283 enabled: true, 284 }, 285 expected: "\ncolor: red;\na: b;\n", 286 }, 287 288 // Newline insertion and indentation. 289 { 290 desc: "indentation with create", 291 input: "\n color: red;\n", 292 instruction: { 293 type: "create", 294 name: "a", 295 value: "b", 296 priority: "", 297 index: 1, 298 enabled: true, 299 }, 300 expected: "\n color: red;\n a: b;\n", 301 }, 302 // Newline insertion and indentation. 303 { 304 desc: "indentation plus semicolon insertion before newline", 305 // Note the lack of a trailing semicolon. 306 input: "\n color: red\n", 307 instruction: { 308 type: "create", 309 name: "a", 310 value: "b", 311 priority: "", 312 index: 1, 313 enabled: true, 314 }, 315 expected: "\n color: red;\n a: b;\n", 316 }, 317 { 318 desc: "indentation inserted before trailing whitespace", 319 // Note the trailing whitespace. This could come from a rule 320 // like: 321 // @supports (mumble) { 322 // body { 323 // color: red; 324 // } 325 // } 326 // Here if we create a rule we don't want it to follow 327 // the indentation of the "}". 328 input: "\n color: red;\n ", 329 instruction: { 330 type: "create", 331 name: "a", 332 value: "b", 333 priority: "", 334 index: 1, 335 enabled: true, 336 }, 337 expected: "\n color: red;\n a: b;\n ", 338 }, 339 // Newline insertion and indentation. 340 { 341 desc: "indentation comes from preceding comment", 342 // Note how the comment comes before the declaration. 343 input: "\n /* comment */ color: red\n", 344 instruction: { 345 type: "create", 346 name: "a", 347 value: "b", 348 priority: "", 349 index: 1, 350 enabled: true, 351 }, 352 expected: "\n /* comment */ color: red;\n a: b;\n", 353 }, 354 // Default indentation. 355 { 356 desc: "use of default indentation", 357 input: "\n", 358 instruction: { 359 type: "create", 360 name: "a", 361 value: "b", 362 priority: "", 363 index: 0, 364 enabled: true, 365 }, 366 expected: "\n\ta: b;\n", 367 }, 368 369 // Deletion handles newlines properly. 370 { 371 desc: "deletion removes newline", 372 input: "a:b;\nc:d;\ne:f;", 373 instruction: { type: "remove", name: "c", index: 1 }, 374 expected: "a:b;\ne:f;", 375 }, 376 // Deletion handles newlines properly. 377 { 378 desc: "deletion remove blank line", 379 input: "\n a:b;\n c:d; \ne:f;", 380 instruction: { type: "remove", name: "c", index: 1 }, 381 expected: "\n a:b;\ne:f;", 382 }, 383 // Deletion handles newlines properly. 384 { 385 desc: "deletion leaves comment", 386 input: "\n a:b;\n /* something */ c:d; \ne:f;", 387 instruction: { type: "remove", name: "c", index: 1 }, 388 expected: "\n a:b;\n /* something */ \ne:f;", 389 }, 390 // Deletion handles newlines properly. 391 { 392 desc: "deletion leaves previous newline", 393 input: "\n a:b;\n c:d; \ne:f;", 394 instruction: { type: "remove", name: "e", index: 2 }, 395 expected: "\n a:b;\n c:d; \n", 396 }, 397 // Deletion handles newlines properly. 398 { 399 desc: "deletion removes trailing whitespace", 400 input: "\n a:b;\n c:d; \n e:f;", 401 instruction: { type: "remove", name: "e", index: 2 }, 402 expected: "\n a:b;\n c:d; \n", 403 }, 404 // Deletion handles newlines properly. 405 { 406 desc: "deletion preserves indentation", 407 input: " a:b;\n c:d; \n e:f;", 408 instruction: { type: "remove", name: "a", index: 0 }, 409 expected: " c:d; \n e:f;", 410 }, 411 412 // Termination insertion corner case. 413 { 414 desc: "enable single quote termination", 415 input: "/* content: 'hi */ color: red;", 416 instruction: { type: "enable", name: "content", value: true, index: 0 }, 417 expected: "content: 'hi'; color: red;", 418 changed: { 0: "'hi'" }, 419 }, 420 // Termination insertion corner case. 421 { 422 desc: "create single quote termination", 423 input: "content: 'hi", 424 instruction: { 425 type: "create", 426 name: "color", 427 value: "red", 428 priority: "", 429 index: 1, 430 enabled: true, 431 }, 432 expected: "content: 'hi';color: red;", 433 changed: { 0: "'hi'" }, 434 }, 435 436 // Termination insertion corner case. 437 { 438 desc: "enable double quote termination", 439 input: '/* content: "hi */ color: red;', 440 instruction: { type: "enable", name: "content", value: true, index: 0 }, 441 expected: 'content: "hi"; color: red;', 442 changed: { 0: '"hi"' }, 443 }, 444 // Termination insertion corner case. 445 { 446 desc: "create double quote termination", 447 input: 'content: "hi', 448 instruction: { 449 type: "create", 450 name: "color", 451 value: "red", 452 priority: "", 453 index: 1, 454 enabled: true, 455 }, 456 expected: 'content: "hi";color: red;', 457 changed: { 0: '"hi"' }, 458 }, 459 460 // Termination insertion corner case. 461 { 462 desc: "enable url termination", 463 input: "/* background-image: url(something.jpg */ color: red;", 464 instruction: { 465 type: "enable", 466 name: "background-image", 467 value: true, 468 index: 0, 469 }, 470 expected: "background-image: url(something.jpg); color: red;", 471 changed: { 0: "url(something.jpg)" }, 472 }, 473 // Termination insertion corner case. 474 { 475 desc: "create url termination", 476 input: "background-image: url(something.jpg", 477 instruction: { 478 type: "create", 479 name: "color", 480 value: "red", 481 priority: "", 482 index: 1, 483 enabled: true, 484 }, 485 expected: "background-image: url(something.jpg);color: red;", 486 changed: { 0: "url(something.jpg)" }, 487 }, 488 489 // Termination insertion corner case. 490 { 491 desc: "enable url single quote termination", 492 input: "/* background-image: url('something.jpg */ color: red;", 493 instruction: { 494 type: "enable", 495 name: "background-image", 496 value: true, 497 index: 0, 498 }, 499 expected: "background-image: url('something.jpg'); color: red;", 500 changed: { 0: "url('something.jpg')" }, 501 }, 502 // Termination insertion corner case. 503 { 504 desc: "create url single quote termination", 505 input: "background-image: url('something.jpg", 506 instruction: { 507 type: "create", 508 name: "color", 509 value: "red", 510 priority: "", 511 index: 1, 512 enabled: true, 513 }, 514 expected: "background-image: url('something.jpg');color: red;", 515 changed: { 0: "url('something.jpg')" }, 516 }, 517 518 // Termination insertion corner case. 519 { 520 desc: "create url double quote termination", 521 input: '/* background-image: url("something.jpg */ color: red;', 522 instruction: { 523 type: "enable", 524 name: "background-image", 525 value: true, 526 index: 0, 527 }, 528 expected: 'background-image: url("something.jpg"); color: red;', 529 changed: { 0: 'url("something.jpg")' }, 530 }, 531 // Termination insertion corner case. 532 { 533 desc: "enable url double quote termination", 534 input: 'background-image: url("something.jpg', 535 instruction: { 536 type: "create", 537 name: "color", 538 value: "red", 539 priority: "", 540 index: 1, 541 enabled: true, 542 }, 543 expected: 'background-image: url("something.jpg");color: red;', 544 changed: { 0: 'url("something.jpg")' }, 545 }, 546 547 // Termination insertion corner case. 548 { 549 desc: "create backslash termination", 550 input: "something: \\", 551 instruction: { 552 type: "create", 553 name: "color", 554 value: "red", 555 priority: "", 556 index: 1, 557 enabled: true, 558 }, 559 expected: "something: \\\\;color: red;", 560 changed: { 0: "\\\\" }, 561 }, 562 563 // Termination insertion corner case. 564 { 565 desc: "enable backslash single quote termination", 566 input: "something: '\\", 567 instruction: { 568 type: "create", 569 name: "color", 570 value: "red", 571 priority: "", 572 index: 1, 573 enabled: true, 574 }, 575 expected: "something: '\\\\';color: red;", 576 changed: { 0: "'\\\\'" }, 577 }, 578 { 579 desc: "enable backslash double quote termination", 580 input: 'something: "\\', 581 instruction: { 582 type: "create", 583 name: "color", 584 value: "red", 585 priority: "", 586 index: 1, 587 enabled: true, 588 }, 589 expected: 'something: "\\\\";color: red;', 590 changed: { 0: '"\\\\"' }, 591 }, 592 593 // Termination insertion corner case. 594 { 595 desc: "enable comment termination", 596 input: "something: blah /* comment ", 597 instruction: { 598 type: "create", 599 name: "color", 600 value: "red", 601 priority: "", 602 index: 1, 603 enabled: true, 604 }, 605 expected: "something: blah /* comment*/; color: red;", 606 }, 607 608 // Rewrite a "heuristic override" comment. 609 { 610 desc: "enable with heuristic override comment", 611 input: "/*! walrus: zebra; */", 612 instruction: { type: "enable", name: "walrus", value: true, index: 0 }, 613 expected: "walrus: zebra;", 614 }, 615 616 // Sanitize a bad value. 617 { 618 desc: "create sanitize unpaired brace", 619 input: "", 620 instruction: { 621 type: "create", 622 name: "p", 623 value: "}", 624 priority: "", 625 index: 0, 626 enabled: true, 627 }, 628 expected: "p: \\};", 629 changed: { 0: "\\}" }, 630 }, 631 // Sanitize a bad value. 632 { 633 desc: "set sanitize unpaired brace", 634 input: "walrus: zebra;", 635 instruction: { 636 type: "set", 637 name: "walrus", 638 value: "{{}}}", 639 priority: "", 640 index: 0, 641 }, 642 expected: "walrus: {{}}\\};", 643 changed: { 0: "{{}}\\}" }, 644 }, 645 // Sanitize a bad value. 646 { 647 desc: "enable sanitize unpaired brace", 648 input: "/*! walrus: }*/", 649 instruction: { type: "enable", name: "walrus", value: true, index: 0 }, 650 expected: "walrus: \\};", 651 changed: { 0: "\\}" }, 652 }, 653 654 // Creating a new declaration does not require an attempt to 655 // terminate a previous commented declaration. 656 { 657 desc: "disabled declaration does not need semicolon insertion", 658 input: "/*! no: semicolon */\n", 659 instruction: { 660 type: "create", 661 name: "walrus", 662 value: "zebra", 663 priority: "", 664 index: 1, 665 enabled: true, 666 }, 667 expected: "/*! no: semicolon */\nwalrus: zebra;\n", 668 changed: {}, 669 }, 670 671 { 672 desc: "create commented-out property", 673 input: "p: v", 674 instruction: { 675 type: "create", 676 name: "shoveler", 677 value: "duck", 678 priority: "", 679 index: 1, 680 enabled: false, 681 }, 682 expected: "p: v;/*! shoveler: duck; */", 683 }, 684 { 685 desc: "disabled create with comment ender in string", 686 input: "", 687 instruction: { 688 type: "create", 689 name: "content", 690 value: "'*/'", 691 priority: "", 692 index: 0, 693 enabled: false, 694 }, 695 expected: "/*! content: '*\\/'; */", 696 }, 697 698 { 699 desc: "delete disabled property", 700 input: "\n a:b;\n /* color:#f06; */\n e:f;", 701 instruction: { type: "remove", name: "color", index: 1 }, 702 expected: "\n a:b;\n e:f;", 703 }, 704 { 705 desc: "delete heuristic-disabled property", 706 input: "\n a:b;\n /*! c:d; */\n e:f;", 707 instruction: { type: "remove", name: "c", index: 1 }, 708 expected: "\n a:b;\n e:f;", 709 }, 710 { 711 desc: "delete disabled property leaving other disabled property", 712 input: "\n a:b;\n /* color:#f06; background-color: seagreen; */\n e:f;", 713 instruction: { type: "remove", name: "color", index: 1 }, 714 expected: "\n a:b;\n /* background-color: seagreen; */\n e:f;", 715 }, 716 717 { 718 desc: "regression test for bug 1328016", 719 input: "position:absolute;top50px;height:50px;width:50px;", 720 instruction: { 721 type: "set", 722 name: "width", 723 value: "60px", 724 priority: "", 725 index: 2, 726 }, 727 expected: "position:absolute;top50px;height:50px;width:60px;", 728 }, 729 730 { 731 desc: "url regression test for bug 1321970", 732 input: "", 733 instruction: { 734 type: "create", 735 name: "p", 736 value: "url(", 737 priority: "", 738 index: 0, 739 enabled: true, 740 }, 741 expected: "p: url();", 742 changed: { 0: "url()" }, 743 }, 744 745 { 746 desc: "url semicolon regression test for bug 1321970", 747 input: "", 748 instruction: { 749 type: "create", 750 name: "p", 751 value: "url(;", 752 priority: "", 753 index: 0, 754 enabled: true, 755 }, 756 expected: "p: url();", 757 changed: { 0: "url()" }, 758 }, 759 760 { 761 desc: "basic regression test for bug 1321970", 762 input: "", 763 instruction: { 764 type: "create", 765 name: "p", 766 value: "(", 767 priority: "", 768 index: 0, 769 enabled: true, 770 }, 771 expected: "p: \\(;", 772 changed: { 0: "\\(" }, 773 }, 774 775 { 776 desc: "unbalanced regression test for bug 1321970", 777 input: "", 778 instruction: { 779 type: "create", 780 name: "p", 781 value: "({[})", 782 priority: "", 783 index: 0, 784 enabled: true, 785 }, 786 expected: "p: ({\\[});", 787 changed: { 0: "({\\[})" }, 788 }, 789 790 { 791 desc: "function regression test for bug 1321970", 792 input: "", 793 instruction: { 794 type: "create", 795 name: "p", 796 value: "func(1,2)", 797 priority: "", 798 index: 0, 799 enabled: true, 800 }, 801 expected: "p: func(1,2);", 802 }, 803 804 { 805 desc: "function regression test for bug 1355233", 806 input: "", 807 instruction: { 808 type: "create", 809 name: "p", 810 value: "func(", 811 priority: "", 812 index: 0, 813 enabled: true, 814 }, 815 expected: "p: func\\(;", 816 changed: { 0: "func\\(" }, 817 }, 818 ]; 819 820 function rewriteDeclarations( 821 win, 822 inputString, 823 instruction, 824 defaultIndentation 825 ) { 826 const rewriter = new RuleRewriter(win, isCssPropertyKnown, null, inputString); 827 rewriter.defaultIndentation = defaultIndentation; 828 829 switch (instruction.type) { 830 case "rename": 831 rewriter.renameProperty( 832 instruction.index, 833 instruction.name, 834 instruction.newName 835 ); 836 break; 837 838 case "enable": 839 rewriter.setPropertyEnabled( 840 instruction.index, 841 instruction.name, 842 instruction.value 843 ); 844 break; 845 846 case "create": 847 rewriter.createProperty( 848 instruction.index, 849 instruction.name, 850 instruction.value, 851 instruction.priority, 852 instruction.enabled 853 ); 854 break; 855 856 case "set": 857 rewriter.setProperty( 858 instruction.index, 859 instruction.name, 860 instruction.value, 861 instruction.priority 862 ); 863 break; 864 865 case "remove": 866 rewriter.removeProperty(instruction.index, instruction.name); 867 break; 868 869 default: 870 throw new Error("unrecognized instruction"); 871 } 872 873 return rewriter.getResult(); 874 } 875 876 function run_test() { 877 const win = 878 Services.appShell.createWindowlessBrowser(false).document.defaultView; 879 880 for (const test of TEST_DATA) { 881 const { changed, text } = rewriteDeclarations( 882 win, 883 test.input, 884 test.instruction, 885 "\t" 886 ); 887 equal(text, test.expected, "output for " + test.desc); 888 889 let expectChanged; 890 if ("changed" in test) { 891 expectChanged = test.changed; 892 } else { 893 expectChanged = {}; 894 } 895 deepEqual(changed, expectChanged, "changed result for " + test.desc); 896 } 897 }