1 #!/usr/bin/env python 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 """Unit tests for Web Development Style Guide checker.""" 7 8 import os 9 import re 10 import sys 11 import unittest 12 13 test_dir = os.path.dirname(os.path.abspath(__file__)) 14 sys.path.extend([ 15 os.path.normpath(os.path.join(test_dir, '..', '..', 'tools')), 16 os.path.join(test_dir), 17 ]) 18 19 import find_depot_tools # pylint: disable=W0611 20 from testing_support.super_mox import SuperMoxTestBase 21 from web_dev_style import resource_checker, css_checker, js_checker # pylint: disable=F0401 22 23 24 def GetHighlight(line, error): 25 """Returns the substring of |line| that is highlighted in |error|.""" 26 error_lines = error.split('\n') 27 highlight = error_lines[error_lines.index(line) + 1] 28 return ''.join(ch1 for (ch1, ch2) in zip(line, highlight) if ch2 == '^') 29 30 31 class ResourceStyleGuideTest(SuperMoxTestBase): 32 def setUp(self): 33 SuperMoxTestBase.setUp(self) 34 35 input_api = self.mox.CreateMockAnything() 36 input_api.re = re 37 output_api = self.mox.CreateMockAnything() 38 self.checker = resource_checker.ResourceChecker(input_api, output_api) 39 40 def ShouldFailIncludeCheck(self, line): 41 """Checks that the '</include>' checker flags |line| as a style error.""" 42 error = self.checker.IncludeCheck(1, line) 43 self.assertNotEqual('', error, 44 'Should be flagged as style error: ' + line) 45 highlight = GetHighlight(line, error).strip() 46 self.assertTrue('include' in highlight and highlight[0] == '<') 47 48 def ShouldPassIncludeCheck(self, line): 49 """Checks that the '</include>' checker doesn't flag |line| as an error.""" 50 self.assertEqual('', self.checker.IncludeCheck(1, line), 51 'Should not be flagged as style error: ' + line) 52 53 def testIncludeFails(self): 54 lines = [ 55 "</include> ", 56 " </include>", 57 " </include> ", 58 ' <include src="blah.js" /> ', 59 '<include src="blee.js"/>', 60 ] 61 for line in lines: 62 self.ShouldFailIncludeCheck(line) 63 64 def testIncludePasses(self): 65 lines = [ 66 '<include src="assert.js">', 67 "<include src='../../assert.js'>", 68 "<i>include src='blah'</i>", 69 "</i>nclude", 70 "</i>include", 71 ] 72 for line in lines: 73 self.ShouldPassIncludeCheck(line) 74 75 76 class JsStyleGuideTest(SuperMoxTestBase): 77 def setUp(self): 78 SuperMoxTestBase.setUp(self) 79 80 input_api = self.mox.CreateMockAnything() 81 input_api.re = re 82 output_api = self.mox.CreateMockAnything() 83 self.checker = js_checker.JSChecker(input_api, output_api) 84 85 def ShouldFailConstCheck(self, line): 86 """Checks that the 'const' checker flags |line| as a style error.""" 87 error = self.checker.ConstCheck(1, line) 88 self.assertNotEqual('', error, 89 'Should be flagged as style error: ' + line) 90 self.assertEqual(GetHighlight(line, error), 'const') 91 92 def ShouldPassConstCheck(self, line): 93 """Checks that the 'const' checker doesn't flag |line| as a style error.""" 94 self.assertEqual('', self.checker.ConstCheck(1, line), 95 'Should not be flagged as style error: ' + line) 96 97 def testConstFails(self): 98 lines = [ 99 "const foo = 'bar';", 100 " const bar = 'foo';", 101 102 # Trying to use |const| as a variable name 103 "var const = 0;", 104 105 "var x = 5; const y = 6;", 106 "for (var i=0, const e=10; i<e; i++) {", 107 "for (const x=0; x<foo; i++) {", 108 "while (const x = 7) {", 109 ] 110 for line in lines: 111 self.ShouldFailConstCheck(line) 112 113 def testConstPasses(self): 114 lines = [ 115 # sanity check 116 "var foo = 'bar'", 117 118 # @const JsDoc tag 119 "/** @const */ var SEVEN = 7;", 120 121 # @const tag in multi-line comment 122 " * @const", 123 " * @const", 124 125 # @constructor tag in multi-line comment 126 " * @constructor", 127 " * @constructor", 128 129 # words containing 'const' 130 "if (foo.constructor) {", 131 "var deconstruction = 'something';", 132 "var madeUpWordconst = 10;", 133 134 # Strings containing the word |const| 135 "var str = 'const at the beginning';", 136 "var str = 'At the end: const';", 137 138 # doing this one with regex is probably not practical 139 #"var str = 'a const in the middle';", 140 ] 141 for line in lines: 142 self.ShouldPassConstCheck(line) 143 144 def ShouldFailChromeSendCheck(self, line): 145 """Checks that the 'chrome.send' checker flags |line| as a style error.""" 146 error = self.checker.ChromeSendCheck(1, line) 147 self.assertNotEqual('', error, 148 'Should be flagged as style error: ' + line) 149 self.assertEqual(GetHighlight(line, error), ', []') 150 151 def ShouldPassChromeSendCheck(self, line): 152 """Checks that the 'chrome.send' checker doesn't flag |line| as a style 153 error. 154 """ 155 self.assertEqual('', self.checker.ChromeSendCheck(1, line), 156 'Should not be flagged as style error: ' + line) 157 158 def testChromeSendFails(self): 159 lines = [ 160 "chrome.send('message', []);", 161 " chrome.send('message', []);", 162 ] 163 for line in lines: 164 self.ShouldFailChromeSendCheck(line) 165 166 def testChromeSendPasses(self): 167 lines = [ 168 "chrome.send('message', constructArgs('foo', []));", 169 " chrome.send('message', constructArgs('foo', []));", 170 "chrome.send('message', constructArgs([]));", 171 " chrome.send('message', constructArgs([]));", 172 ] 173 for line in lines: 174 self.ShouldPassChromeSendCheck(line) 175 176 def ShouldFailEndJsDocCommentCheck(self, line): 177 """Checks that the **/ checker flags |line| as a style error.""" 178 error = self.checker.EndJsDocCommentCheck(1, line) 179 self.assertNotEqual('', error, 180 'Should be flagged as style error: ' + line) 181 self.assertEqual(GetHighlight(line, error), '**/') 182 183 def ShouldPassEndJsDocCommentCheck(self, line): 184 """Checks that the **/ checker doesn't flag |line| as a style error.""" 185 self.assertEqual('', self.checker.EndJsDocCommentCheck(1, line), 186 'Should not be flagged as style error: ' + line) 187 188 def testEndJsDocCommentFails(self): 189 lines = [ 190 "/** @override **/", 191 "/** @type {number} @const **/", 192 " **/", 193 "**/ ", 194 ] 195 for line in lines: 196 self.ShouldFailEndJsDocCommentCheck(line) 197 198 def testEndJsDocCommentPasses(self): 199 lines = [ 200 "/***************/", # visual separators 201 " */", # valid JSDoc comment ends 202 "*/ ", 203 "/**/", # funky multi-line comment enders 204 "/** @override */", # legit JSDoc one-liners 205 ] 206 for line in lines: 207 self.ShouldPassEndJsDocCommentCheck(line) 208 209 def ShouldFailGetElementByIdCheck(self, line): 210 """Checks that the 'getElementById' checker flags |line| as a style 211 error. 212 """ 213 error = self.checker.GetElementByIdCheck(1, line) 214 self.assertNotEqual('', error, 215 'Should be flagged as style error: ' + line) 216 self.assertEqual(GetHighlight(line, error), 'document.getElementById') 217 218 def ShouldPassGetElementByIdCheck(self, line): 219 """Checks that the 'getElementById' checker doesn't flag |line| as a style 220 error. 221 """ 222 self.assertEqual('', self.checker.GetElementByIdCheck(1, line), 223 'Should not be flagged as style error: ' + line) 224 225 def testGetElementByIdFails(self): 226 lines = [ 227 "document.getElementById('foo');", 228 " document.getElementById('foo');", 229 "var x = document.getElementById('foo');", 230 "if (document.getElementById('foo').hidden) {", 231 ] 232 for line in lines: 233 self.ShouldFailGetElementByIdCheck(line) 234 235 def testGetElementByIdPasses(self): 236 lines = [ 237 "elem.ownerDocument.getElementById('foo');", 238 " elem.ownerDocument.getElementById('foo');", 239 "var x = elem.ownerDocument.getElementById('foo');", 240 "if (elem.ownerDocument.getElementById('foo').hidden) {", 241 "doc.getElementById('foo');", 242 " doc.getElementById('foo');", 243 "cr.doc.getElementById('foo');", 244 " cr.doc.getElementById('foo');", 245 "var x = doc.getElementById('foo');", 246 "if (doc.getElementById('foo').hidden) {", 247 ] 248 for line in lines: 249 self.ShouldPassGetElementByIdCheck(line) 250 251 def ShouldFailInheritDocCheck(self, line): 252 """Checks that the '@inheritDoc' checker flags |line| as a style error.""" 253 error = self.checker.InheritDocCheck(1, line) 254 self.assertNotEqual('', error, 255 msg='Should be flagged as style error: ' + line) 256 self.assertEqual(GetHighlight(line, error), '@inheritDoc') 257 258 def ShouldPassInheritDocCheck(self, line): 259 """Checks that the '@inheritDoc' checker doesn't flag |line| as a style 260 error. 261 """ 262 self.assertEqual('', self.checker.InheritDocCheck(1, line), 263 msg='Should not be flagged as style error: ' + line) 264 265 def testInheritDocFails(self): 266 lines = [ 267 " /** @inheritDoc */", 268 " * @inheritDoc", 269 ] 270 for line in lines: 271 self.ShouldFailInheritDocCheck(line) 272 273 def testInheritDocPasses(self): 274 lines = [ 275 "And then I said, but I won't @inheritDoc! Hahaha!", 276 " If your dad's a doctor, do you inheritDoc?", 277 " What's up, inherit doc?", 278 " this.inheritDoc(someDoc)", 279 ] 280 for line in lines: 281 self.ShouldPassInheritDocCheck(line) 282 283 def ShouldFailWrapperTypeCheck(self, line): 284 """Checks that the use of wrapper types (i.e. new Number(), @type {Number}) 285 is a style error. 286 """ 287 error = self.checker.WrapperTypeCheck(1, line) 288 self.assertNotEqual('', error, 289 msg='Should be flagged as style error: ' + line) 290 highlight = GetHighlight(line, error) 291 self.assertTrue(highlight in ('Boolean', 'Number', 'String')) 292 293 def ShouldPassWrapperTypeCheck(self, line): 294 """Checks that the wrapper type checker doesn't flag |line| as a style 295 error. 296 """ 297 self.assertEqual('', self.checker.WrapperTypeCheck(1, line), 298 msg='Should not be flagged as style error: ' + line) 299 300 def testWrapperTypePasses(self): 301 lines = [ 302 "/** @param {!ComplexType} */", 303 " * @type {Object}", 304 " * @param {Function=} opt_callback", 305 " * @param {} num Number of things to add to {blah}.", 306 " * @return {!print_preview.PageNumberSet}", 307 " /* @returns {Number} */", # Should be /** @return {Number} */ 308 "* @param {!LocalStrings}" 309 " Your type of Boolean is false!", 310 " Then I parameterized her Number from her friend!", 311 " A String of Pearls", 312 " types.params.aBoolean.typeString(someNumber)", 313 ] 314 for line in lines: 315 self.ShouldPassWrapperTypeCheck(line) 316 317 def testWrapperTypeFails(self): 318 lines = [ 319 " /**@type {String}*/(string)", 320 " * @param{Number=} opt_blah A number", 321 "/** @private @return {!Boolean} */", 322 " * @param {number|String}", 323 ] 324 for line in lines: 325 self.ShouldFailWrapperTypeCheck(line) 326 327 def ShouldFailVarNameCheck(self, line): 328 """Checks that var unix_hacker, $dollar are style errors.""" 329 error = self.checker.VarNameCheck(1, line) 330 self.assertNotEqual('', error, 331 msg='Should be flagged as style error: ' + line) 332 highlight = GetHighlight(line, error) 333 self.assertFalse('var ' in highlight); 334 335 def ShouldPassVarNameCheck(self, line): 336 """Checks that variableNamesLikeThis aren't style errors.""" 337 self.assertEqual('', self.checker.VarNameCheck(1, line), 338 msg='Should not be flagged as style error: ' + line) 339 340 def testVarNameFails(self): 341 lines = [ 342 "var private_;", 343 " var _super_private", 344 " var unix_hacker = someFunc();", 345 ] 346 for line in lines: 347 self.ShouldFailVarNameCheck(line) 348 349 def testVarNamePasses(self): 350 lines = [ 351 " var namesLikeThis = [];", 352 " for (var i = 0; i < 10; ++i) { ", 353 "for (var i in obj) {", 354 " var one, two, three;", 355 " var magnumPI = {};", 356 " var g_browser = 'da browzer';", 357 "/** @const */ var Bla = options.Bla;", # goog.scope() replacement. 358 " var $ = function() {", # For legacy reasons. 359 " var StudlyCaps = cr.define('bla')", # Classes. 360 " var SCARE_SMALL_CHILDREN = [", # TODO(dbeam): add @const in 361 # front of all these vars like 362 "/** @const */ CONST_VAR = 1;", # this line has (<--). 363 ] 364 for line in lines: 365 self.ShouldPassVarNameCheck(line) 366 367 368 class ClosureLintTest(SuperMoxTestBase): 369 def setUp(self): 370 SuperMoxTestBase.setUp(self) 371 372 input_api = self.mox.CreateMockAnything() 373 input_api.os_path = os.path 374 input_api.re = re 375 376 input_api.change = self.mox.CreateMockAnything() 377 self.mox.StubOutWithMock(input_api.change, 'RepositoryRoot') 378 src_root = os.path.join(os.path.dirname(__file__), '..', '..') 379 input_api.change.RepositoryRoot().MultipleTimes().AndReturn(src_root) 380 381 output_api = self.mox.CreateMockAnything() 382 383 self.mox.ReplayAll() 384 385 self.checker = js_checker.JSChecker(input_api, output_api) 386 387 def ShouldPassClosureLint(self, source): 388 errors = self.checker.ClosureLint('', source=source) 389 390 for error in errors: 391 print 'Error: ' + error.message 392 393 self.assertListEqual([], errors) 394 395 def testBindFalsePositives(self): 396 sources = [ 397 [ 398 'var addOne = function(prop) {\n', 399 ' this[prop] += 1;\n', 400 '}.bind(counter, timer);\n', 401 '\n', 402 'setInterval(addOne, 1000);\n', 403 '\n', 404 ], 405 [ 406 '/** Da clickz. */\n', 407 'button.onclick = function() { this.add_(this.total_); }.bind(this);\n', 408 ], 409 ] 410 for source in sources: 411 self.ShouldPassClosureLint(source) 412 413 def testPromiseFalsePositives(self): 414 sources = [ 415 [ 416 'Promise.reject(1).catch(function(error) {\n', 417 ' alert(error);\n', 418 '});\n', 419 ], 420 [ 421 'var loaded = new Promise();\n', 422 'loaded.then(runAwesomeApp);\n', 423 'loaded.catch(showSadFace);\n', 424 '\n', 425 '/** Da loadz. */\n', 426 'document.onload = function() { loaded.resolve(); };\n', 427 '\n', 428 '/** Da errorz. */\n', 429 'document.onerror = function() { loaded.reject(); };\n', 430 '\n', 431 "if (document.readystate == 'complete') loaded.resolve();\n", 432 ], 433 ] 434 for source in sources: 435 self.ShouldPassClosureLint(source) 436 437 438 class CssStyleGuideTest(SuperMoxTestBase): 439 def setUp(self): 440 SuperMoxTestBase.setUp(self) 441 442 self.fake_file_name = 'fake.css' 443 444 self.fake_file = self.mox.CreateMockAnything() 445 self.mox.StubOutWithMock(self.fake_file, 'LocalPath') 446 self.fake_file.LocalPath().AndReturn(self.fake_file_name) 447 # Actual calls to NewContents() are defined in each test. 448 self.mox.StubOutWithMock(self.fake_file, 'NewContents') 449 450 self.input_api = self.mox.CreateMockAnything() 451 self.input_api.re = re 452 self.mox.StubOutWithMock(self.input_api, 'AffectedSourceFiles') 453 self.input_api.AffectedFiles( 454 include_deletes=False, file_filter=None).AndReturn([self.fake_file]) 455 456 # Actual creations of PresubmitPromptWarning are defined in each test. 457 self.output_api = self.mox.CreateMockAnything() 458 self.mox.StubOutWithMock(self.output_api, 'PresubmitPromptWarning', 459 use_mock_anything=True) 460 461 self.output_api = self.mox.CreateMockAnything() 462 self.mox.StubOutWithMock(self.output_api, 'PresubmitNotifyResult', 463 use_mock_anything=True) 464 465 def VerifyContentsIsValid(self, contents): 466 self.fake_file.NewContents().AndReturn(contents.splitlines()) 467 self.mox.ReplayAll() 468 css_checker.CSSChecker(self.input_api, self.output_api).RunChecks() 469 470 def VerifyContentsProducesOutput(self, contents, output): 471 self.fake_file.NewContents().AndReturn(contents.splitlines()) 472 author_msg = ('Was the CSS checker useful? ' 473 'Send feedback or hate mail to dbeam (at] chromium.org.') 474 self.output_api.PresubmitNotifyResult(author_msg).AndReturn(None) 475 self.output_api.PresubmitPromptWarning( 476 self.fake_file_name + ':\n' + output.strip()).AndReturn(None) 477 self.mox.ReplayAll() 478 css_checker.CSSChecker(self.input_api, self.output_api).RunChecks() 479 480 def testCssAlphaWithAtBlock(self): 481 self.VerifyContentsProducesOutput(""" 482 <include src="../shared/css/cr/ui/overlay.css"> 483 <include src="chrome://resources/totally-cool.css" /> 484 485 /* A hopefully safely ignored comment and @media statement. /**/ 486 @media print { 487 div { 488 display: block; 489 color: red; 490 } 491 } 492 493 .rule { 494 z-index: 5; 495 <if expr="not is macosx"> 496 background-image: url(chrome://resources/BLAH); /* TODO(dbeam): Fix this. */ 497 background-color: rgb(235, 239, 249); 498 </if> 499 <if expr="is_macosx"> 500 background-color: white; 501 background-image: url(chrome://resources/BLAH2); 502 </if> 503 color: black; 504 } 505 506 <if expr="is_macosx"> 507 .language-options-right { 508 visibility: hidden; 509 opacity: 1; /* TODO(dbeam): Fix this. */ 510 } 511 </if>""", """ 512 - Alphabetize properties and list vendor specific (i.e. -webkit) above standard. 513 display: block; 514 color: red; 515 516 z-index: 5; 517 color: black;""") 518 519 def testCssStringWithAt(self): 520 self.VerifyContentsIsValid(""" 521 #logo { 522 background-image: url('images/google_logo.png@2x'); 523 } 524 525 body.alternate-logo #logo { 526 -webkit-mask-image: url('images/google_logo.png@2x'); 527 background: none; 528 } 529 530 .stuff1 { 531 } 532 533 .stuff2 { 534 } 535 """) 536 537 def testCssAlphaWithNonStandard(self): 538 self.VerifyContentsProducesOutput(""" 539 div { 540 /* A hopefully safely ignored comment and @media statement. /**/ 541 color: red; 542 -webkit-margin-start: 5px; 543 }""", """ 544 - Alphabetize properties and list vendor specific (i.e. -webkit) above standard. 545 color: red; 546 -webkit-margin-start: 5px;""") 547 548 def testCssAlphaWithLongerDashedProps(self): 549 self.VerifyContentsProducesOutput(""" 550 div { 551 border-left: 5px; /* A hopefully removed comment. */ 552 border: 5px solid red; 553 }""", """ 554 - Alphabetize properties and list vendor specific (i.e. -webkit) above standard. 555 border-left: 5px; 556 border: 5px solid red;""") 557 558 def testCssBracesHaveSpaceBeforeAndNothingAfter(self): 559 self.VerifyContentsProducesOutput(""" 560 /* Hello! */div/* Comment here*/{ 561 display: block; 562 } 563 564 blah /* hey! */ 565 { 566 rule: value; 567 } 568 569 .this.is { /* allowed */ 570 rule: value; 571 }""", """ 572 - Start braces ({) end a selector, have a space before them and no rules after. 573 div{ 574 {""") 575 576 def testCssClassesUseDashes(self): 577 self.VerifyContentsProducesOutput(""" 578 .className, 579 .ClassName, 580 .class-name /* We should not catch this. */, 581 .class_name { 582 display: block; 583 }""", """ 584 - Classes use .dash-form. 585 .className, 586 .ClassName, 587 .class_name {""") 588 589 def testCssCloseBraceOnNewLine(self): 590 self.VerifyContentsProducesOutput(""" 591 @media { /* TODO(dbeam) Fix this case. */ 592 .rule { 593 display: block; 594 }} 595 596 @-webkit-keyframe blah { 597 100% { height: -500px 0; } 598 } 599 600 #rule { 601 rule: value; }""", """ 602 - Always put a rule closing brace (}) on a new line. 603 rule: value; }""") 604 605 def testCssColonsHaveSpaceAfter(self): 606 self.VerifyContentsProducesOutput(""" 607 div:not(.class):not([attr=5]), /* We should not catch this. */ 608 div:not(.class):not([attr]) /* Nor this. */ { 609 background: url(data:image/jpeg,asdfasdfsadf); /* Ignore this. */ 610 background: -webkit-linear-gradient(left, red, 611 80% blah blee blar); 612 color: red; 613 display:block; 614 }""", """ 615 - Colons (:) should have a space after them. 616 display:block; 617 618 - Don't use data URIs in source files. Use grit instead. 619 background: url(data:image/jpeg,asdfasdfsadf);""") 620 621 def testCssFavorSingleQuotes(self): 622 self.VerifyContentsProducesOutput(""" 623 html[dir="rtl"] body, 624 html[dir=ltr] body /* TODO(dbeam): Require '' around rtl in future? */ { 625 background: url("chrome://resources/BLAH"); 626 font-family: "Open Sans"; 627 <if expr="is_macosx"> 628 blah: blee; 629 </if> 630 }""", """ 631 - Use single quotes (') instead of double quotes (") in strings. 632 html[dir="rtl"] body, 633 background: url("chrome://resources/BLAH"); 634 font-family: "Open Sans";""") 635 636 def testCssHexCouldBeShorter(self): 637 self.VerifyContentsProducesOutput(""" 638 #abc, 639 #abc-, 640 #abc-ghij, 641 #abcdef-, 642 #abcdef-ghij, 643 #aaaaaa, 644 #bbaacc { 645 background-color: #336699; /* Ignore short hex rule if not gray. */ 646 color: #999999; 647 color: #666; 648 }""", """ 649 - Use abbreviated hex (#rgb) when in form #rrggbb. 650 color: #999999; (replace with #999) 651 652 - Use rgb() over #hex when not a shade of gray (like #333). 653 background-color: #336699; (replace with rgb(51, 102, 153))""") 654 655 def testCssUseMillisecondsForSmallTimes(self): 656 self.VerifyContentsProducesOutput(""" 657 .transition-0s /* This is gross but may happen. */ { 658 transform: one 0.2s; 659 transform: two .1s; 660 transform: tree 1s; 661 transform: four 300ms; 662 }""", """ 663 - Use milliseconds for time measurements under 1 second. 664 transform: one 0.2s; (replace with 200ms) 665 transform: two .1s; (replace with 100ms)""") 666 667 def testCssNoDataUrisInSourceFiles(self): 668 self.VerifyContentsProducesOutput(""" 669 img { 670 background: url( data:image/jpeg,4\/\/350|\/|3|2 ); 671 background: url('data:image/jpeg,4\/\/350|\/|3|2'); 672 }""", """ 673 - Don't use data URIs in source files. Use grit instead. 674 background: url( data:image/jpeg,4\/\/350|\/|3|2 ); 675 background: url('data:image/jpeg,4\/\/350|\/|3|2');""") 676 677 def testCssOneRulePerLine(self): 678 self.VerifyContentsProducesOutput(""" 679 a:not([hidden]):not(.custom-appearance):not([version=1]):first-of-type, 680 a:not([hidden]):not(.custom-appearance):not([version=1]):first-of-type ~ 681 input[type='checkbox']:not([hidden]), 682 div { 683 background: url(chrome://resources/BLAH); 684 rule: value; /* rule: value; */ 685 rule: value; rule: value; 686 }""", """ 687 - One rule per line (what not to do: color: red; margin: 0;). 688 rule: value; rule: value;""") 689 690 def testCssOneSelectorPerLine(self): 691 self.VerifyContentsProducesOutput(""" 692 a, 693 div,a, 694 div,/* Hello! */ span, 695 #id.class([dir=rtl):not(.class):any(a, b, d) { 696 rule: value; 697 } 698 699 a, 700 div,a { 701 some-other: rule here; 702 }""", """ 703 - One selector per line (what not to do: a, b {}). 704 div,a, 705 div, span, 706 div,a {""") 707 708 def testCssPseudoElementDoubleColon(self): 709 self.VerifyContentsProducesOutput(""" 710 a:href, 711 br::after, 712 ::-webkit-scrollbar-thumb, 713 a:not([empty]):hover:focus:active, /* shouldn't catch here and above */ 714 abbr:after, 715 .tree-label:empty:after, 716 b:before, 717 :-WebKit-ScrollBar { 718 rule: value; 719 }""", """ 720 - Pseudo-elements should use double colon (i.e. ::after). 721 :after (should be ::after) 722 :after (should be ::after) 723 :before (should be ::before) 724 :-WebKit-ScrollBar (should be ::-WebKit-ScrollBar) 725 """) 726 727 def testCssRgbIfNotGray(self): 728 self.VerifyContentsProducesOutput(""" 729 #abc, 730 #aaa, 731 #aabbcc { 732 background: -webkit-linear-gradient(left, from(#abc), to(#def)); 733 color: #bad; 734 color: #bada55; 735 }""", """ 736 - Use rgb() over #hex when not a shade of gray (like #333). 737 background: -webkit-linear-gradient(left, from(#abc), to(#def)); """ 738 """(replace with rgb(170, 187, 204), rgb(221, 238, 255)) 739 color: #bad; (replace with rgb(187, 170, 221)) 740 color: #bada55; (replace with rgb(186, 218, 85))""") 741 742 def testCssZeroLengthTerms(self): 743 self.VerifyContentsProducesOutput(""" 744 @-webkit-keyframe anim { 745 0% { /* Ignore key frames */ 746 width: 0px; 747 } 748 10% { 749 width: 10px; 750 } 751 100% { 752 width: 100px; 753 } 754 } 755 756 /* http://crbug.com/359682 */ 757 #spinner-container #spinner { 758 -webkit-animation-duration: 1.0s; 759 } 760 761 .media-button.play > .state0.active, 762 .media-button[state='0'] > .state0.normal /* blah */, /* blee */ 763 .media-button[state='0']:not(.disabled):hover > .state0.hover { 764 -webkit-animation: anim 0s; 765 -webkit-animation-duration: anim 0ms; 766 -webkit-transform: scale(0%), 767 translateX(0deg), 768 translateY(0rad), 769 translateZ(0grad); 770 background-position-x: 0em; 771 background-position-y: 0ex; 772 border-width: 0em; 773 color: hsl(0, 0%, 85%); /* Shouldn't trigger error. */ 774 opacity: .0; 775 opacity: 0.0; 776 opacity: 0.; 777 } 778 779 @page { 780 border-width: 0mm; 781 height: 0cm; 782 width: 0in; 783 }""", """ 784 - Make all zero length terms (i.e. 0px) 0 unless inside of hsl() or part of""" 785 """ @keyframe. 786 width: 0px; 787 -webkit-animation: anim 0s; 788 -webkit-animation-duration: anim 0ms; 789 -webkit-transform: scale(0%), 790 translateX(0deg), 791 translateY(0rad), 792 translateZ(0grad); 793 background-position-x: 0em; 794 background-position-y: 0ex; 795 border-width: 0em; 796 opacity: .0; 797 opacity: 0.0; 798 opacity: 0.; 799 border-width: 0mm; 800 height: 0cm; 801 width: 0in; 802 """) 803 804 if __name__ == '__main__': 805 unittest.main() 806