Home | History | Annotate | Download | only in browser
      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