| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 #!/usr/bin/env python |  | 
| 2 # Copyright 2015 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 import css_checker |  | 
| 7 from os import path as os_path |  | 
| 8 import re |  | 
| 9 from sys import path as sys_path |  | 
| 10 import unittest |  | 
| 11 |  | 
| 12 _HERE = os_path.dirname(os_path.abspath(__file__)) |  | 
| 13 sys_path.append(os_path.join(_HERE, '..', '..', '..', 'build')) |  | 
| 14 |  | 
| 15 import find_depot_tools  # pylint: disable=W0611 |  | 
| 16 from testing_support.super_mox import SuperMoxTestBase |  | 
| 17 |  | 
| 18 |  | 
| 19 class CssCheckerTest(SuperMoxTestBase): |  | 
| 20   def setUp(self): |  | 
| 21     SuperMoxTestBase.setUp(self) |  | 
| 22 |  | 
| 23     self.fake_file = self.mox.CreateMockAnything() |  | 
| 24     # Actual calls to NewContents() and LocalPath() are defined in each test. |  | 
| 25     self.mox.StubOutWithMock(self.fake_file, 'LocalPath') |  | 
| 26     self.mox.StubOutWithMock(self.fake_file, 'NewContents') |  | 
| 27 |  | 
| 28     self.input_api = self.mox.CreateMockAnything() |  | 
| 29     self.input_api.re = re |  | 
| 30     self.mox.StubOutWithMock(self.input_api, 'AffectedSourceFiles') |  | 
| 31     self.input_api.AffectedFiles( |  | 
| 32         include_deletes=False, file_filter=None).AndReturn([self.fake_file]) |  | 
| 33 |  | 
| 34     # Actual creations of PresubmitPromptWarning are defined in each test. |  | 
| 35     self.output_api = self.mox.CreateMockAnything() |  | 
| 36     self.mox.StubOutWithMock(self.output_api, 'PresubmitPromptWarning', |  | 
| 37                              use_mock_anything=True) |  | 
| 38 |  | 
| 39     self.output_api = self.mox.CreateMockAnything() |  | 
| 40     self.mox.StubOutWithMock(self.output_api, 'PresubmitNotifyResult', |  | 
| 41                              use_mock_anything=True) |  | 
| 42 |  | 
| 43   def _create_file(self, contents, filename): |  | 
| 44     self.fake_file_name = filename |  | 
| 45     self.fake_file.LocalPath().AndReturn(self.fake_file_name) |  | 
| 46     self.fake_file.NewContents().AndReturn(contents.splitlines()) |  | 
| 47 |  | 
| 48   def VerifyContentIsValid(self, contents, filename='fake.css'): |  | 
| 49     self._create_file(contents, filename) |  | 
| 50     self.mox.ReplayAll() |  | 
| 51     css_checker.CSSChecker(self.input_api, self.output_api).RunChecks() |  | 
| 52 |  | 
| 53   def VerifyContentsProducesOutput(self, contents, output, filename='fake.css'): |  | 
| 54     self._create_file(contents, filename) |  | 
| 55     self.output_api.PresubmitPromptWarning( |  | 
| 56         self.fake_file_name + ':\n' + output.strip()).AndReturn(None) |  | 
| 57     self.mox.ReplayAll() |  | 
| 58     css_checker.CSSChecker(self.input_api, self.output_api).RunChecks() |  | 
| 59 |  | 
| 60   def testCssAlphaWithAtBlock(self): |  | 
| 61     self.VerifyContentsProducesOutput(""" |  | 
| 62 <include src="../shared/css/cr/ui/overlay.css"> |  | 
| 63 <include src="chrome://resources/totally-cool.css" /> |  | 
| 64 |  | 
| 65 /* A hopefully safely ignored comment and @media statement. /**/ |  | 
| 66 @media print { |  | 
| 67   div { |  | 
| 68     display: block; |  | 
| 69     color: red; |  | 
| 70   } |  | 
| 71 } |  | 
| 72 |  | 
| 73 .rule { |  | 
| 74   z-index: 5; |  | 
| 75 <if expr="not is macosx"> |  | 
| 76   background-image: url(chrome://resources/BLAH); /* TODO(dbeam): Fix this. */ |  | 
| 77   background-color: rgb(235, 239, 249); |  | 
| 78 </if> |  | 
| 79 <if expr="is_macosx"> |  | 
| 80   background-color: white; |  | 
| 81   background-image: url(chrome://resources/BLAH2); |  | 
| 82 </if> |  | 
| 83   color: black; |  | 
| 84 } |  | 
| 85 |  | 
| 86 <if expr="is_macosx"> |  | 
| 87 .language-options-right { |  | 
| 88   visibility: hidden; |  | 
| 89   opacity: 1; /* TODO(dbeam): Fix this. */ |  | 
| 90 } |  | 
| 91 </if>""", """ |  | 
| 92 - Alphabetize properties and list vendor specific (i.e. -webkit) above standard. |  | 
| 93     display: block; |  | 
| 94     color: red; |  | 
| 95 |  | 
| 96     z-index: 5; |  | 
| 97     color: black;""") |  | 
| 98 |  | 
| 99   def testCssStringWithAt(self): |  | 
| 100     self.VerifyContentIsValid(""" |  | 
| 101 #logo { |  | 
| 102   background-image: url(images/google_logo.png@2x); |  | 
| 103 } |  | 
| 104 |  | 
| 105 body.alternate-logo #logo { |  | 
| 106   -webkit-mask-image: url(images/google_logo.png@2x); |  | 
| 107   background: none; |  | 
| 108   @apply(--some-variable); |  | 
| 109 } |  | 
| 110 |  | 
| 111 div { |  | 
| 112   -webkit-margin-start: 5px; |  | 
| 113 } |  | 
| 114 |  | 
| 115 .stuff1 { |  | 
| 116 } |  | 
| 117 |  | 
| 118 .stuff2 { |  | 
| 119 } |  | 
| 120       """) |  | 
| 121 |  | 
| 122   def testCssAlphaWithNonStandard(self): |  | 
| 123     self.VerifyContentsProducesOutput(""" |  | 
| 124 div { |  | 
| 125   /* A hopefully safely ignored comment and @media statement. /**/ |  | 
| 126   color: red; |  | 
| 127   -webkit-margin-start: 5px; |  | 
| 128 }""", """ |  | 
| 129 - Alphabetize properties and list vendor specific (i.e. -webkit) above standard. |  | 
| 130     color: red; |  | 
| 131     -webkit-margin-start: 5px;""") |  | 
| 132 |  | 
| 133   def testCssAlphaWithLongerDashedProps(self): |  | 
| 134     self.VerifyContentsProducesOutput(""" |  | 
| 135 div { |  | 
| 136   border-left: 5px;  /* A hopefully removed comment. */ |  | 
| 137   border: 5px solid red; |  | 
| 138 }""", """ |  | 
| 139 - Alphabetize properties and list vendor specific (i.e. -webkit) above standard. |  | 
| 140     border-left: 5px; |  | 
| 141     border: 5px solid red;""") |  | 
| 142 |  | 
| 143   def testCssAlphaWithVariables(self): |  | 
| 144     self.VerifyContentIsValid(""" |  | 
| 145 #id { |  | 
| 146   --zzyxx-xylophone: 3px; |  | 
| 147   --ignore-me: { |  | 
| 148     /* TODO(dbeam): fix this by creating a "sort context". If we simply strip |  | 
| 149      * off the mixin, the inside contents will be compared to the outside |  | 
| 150      * contents, which isn't what we want. */ |  | 
| 151     visibility: hidden; |  | 
| 152     color: black; |  | 
| 153   }; |  | 
| 154   --aardvark-animal: var(--zzyxz-xylophone); |  | 
| 155 } |  | 
| 156 """) |  | 
| 157 |  | 
| 158   def testCssBracesHaveSpaceBeforeAndNothingAfter(self): |  | 
| 159     self.VerifyContentsProducesOutput(""" |  | 
| 160 /* Hello! */div/* Comment here*/{ |  | 
| 161   display: block; |  | 
| 162 } |  | 
| 163 |  | 
| 164 blah /* hey! */ |  | 
| 165 { |  | 
| 166   rule: value; |  | 
| 167 } |  | 
| 168 |  | 
| 169 .mixed-in { |  | 
| 170   display: none; |  | 
| 171   --css-mixin: { |  | 
| 172     color: red; |  | 
| 173   };  /* This should be ignored. */ |  | 
| 174 } |  | 
| 175 |  | 
| 176 .this.is { /* allowed */ |  | 
| 177   rule: value; |  | 
| 178 }""", """ |  | 
| 179 - Start braces ({) end a selector, have a space before them and no rules after. |  | 
| 180     div{ |  | 
| 181     {""") |  | 
| 182 |  | 
| 183   def testCssClassesUseDashes(self): |  | 
| 184     self.VerifyContentsProducesOutput(""" |  | 
| 185 .className, |  | 
| 186 .ClassName, |  | 
| 187 .class-name /* We should not catch this. */, |  | 
| 188 .class_name { |  | 
| 189   display: block; |  | 
| 190 }""", """ |  | 
| 191  - Classes use .dash-form. |  | 
| 192     .className, |  | 
| 193     .ClassName, |  | 
| 194     .class_name {""") |  | 
| 195 |  | 
| 196   def testCssCloseBraceOnNewLine(self): |  | 
| 197     self.VerifyContentsProducesOutput(""" |  | 
| 198 @media { /* TODO(dbeam) Fix this case. */ |  | 
| 199   .rule { |  | 
| 200     display: block; |  | 
| 201   }} |  | 
| 202 |  | 
| 203 @-webkit-keyframe blah { |  | 
| 204   from { height: rotate(-10turn); } |  | 
| 205   100% { height: 500px; } |  | 
| 206 } |  | 
| 207 |  | 
| 208 #id { /* $i18n{*} and $i18nRaw{*} should be ignored. */ |  | 
| 209   rule: $i18n{someValue}; |  | 
| 210   rule2: $i18nRaw{someValue}; |  | 
| 211   --css-mixin: { |  | 
| 212     color: red; |  | 
| 213   }; |  | 
| 214 } |  | 
| 215 |  | 
| 216 .paper-wrapper { |  | 
| 217   --paper-thinger: { |  | 
| 218     background: blue; |  | 
| 219   }; |  | 
| 220 } |  | 
| 221 |  | 
| 222 #rule { |  | 
| 223   rule: value; }""", """ |  | 
| 224 - Always put a rule closing brace (}) on a new line. |  | 
| 225     rule: value; }""") |  | 
| 226 |  | 
| 227   def testCssColonsHaveSpaceAfter(self): |  | 
| 228     self.VerifyContentsProducesOutput(""" |  | 
| 229 div:not(.class):not([attr=5]), /* We should not catch this. */ |  | 
| 230 div:not(.class):not([attr]) /* Nor this. */ { |  | 
| 231   background: url(data:image/jpeg,asdfasdfsadf); /* Ignore this. */ |  | 
| 232   background: -webkit-linear-gradient(left, red, |  | 
| 233                                       80% blah blee blar); |  | 
| 234   color: red; |  | 
| 235   display:block; |  | 
| 236 }""", """ |  | 
| 237 - Colons (:) should have a space after them. |  | 
| 238     display:block; |  | 
| 239 |  | 
| 240 - Don't use data URIs in source files. Use grit instead. |  | 
| 241     background: url(data:image/jpeg,asdfasdfsadf);""") |  | 
| 242 |  | 
| 243   def testCssFavorSingleQuotes(self): |  | 
| 244     self.VerifyContentsProducesOutput(""" |  | 
| 245 html[dir="rtl"] body, |  | 
| 246 html[dir=ltr] body /* TODO(dbeam): Require '' around rtl in future? */ { |  | 
| 247   font-family: "Open Sans"; |  | 
| 248 <if expr="is_macosx"> |  | 
| 249   blah: blee; |  | 
| 250 </if> |  | 
| 251 }""", """ |  | 
| 252 - Use single quotes (') instead of double quotes (") in strings. |  | 
| 253     html[dir="rtl"] body, |  | 
| 254     font-family: "Open Sans";""") |  | 
| 255 |  | 
| 256   def testCssHexCouldBeShorter(self): |  | 
| 257     self.VerifyContentsProducesOutput(""" |  | 
| 258 #abc, |  | 
| 259 #abc-, |  | 
| 260 #abc-ghij, |  | 
| 261 #abcdef-, |  | 
| 262 #abcdef-ghij, |  | 
| 263 #aaaaaa, |  | 
| 264 #bbaacc { |  | 
| 265   background-color: #336699; /* Ignore short hex rule if not gray. */ |  | 
| 266   color: #999999; |  | 
| 267   color: #666; |  | 
| 268 }""", """ |  | 
| 269 - Use abbreviated hex (#rgb) when in form #rrggbb. |  | 
| 270     color: #999999; (replace with #999) |  | 
| 271 |  | 
| 272 - Use rgb() over #hex when not a shade of gray (like #333). |  | 
| 273     background-color: #336699; (replace with rgb(51, 102, 153))""") |  | 
| 274 |  | 
| 275   def testCssUseMillisecondsForSmallTimes(self): |  | 
| 276     self.VerifyContentsProducesOutput(""" |  | 
| 277 .transition-0s /* This is gross but may happen. */ { |  | 
| 278   transform: one 0.2s; |  | 
| 279   transform: two .1s; |  | 
| 280   transform: tree 1s; |  | 
| 281   transform: four 300ms; |  | 
| 282 }""", """ |  | 
| 283 - Use milliseconds for time measurements under 1 second. |  | 
| 284     transform: one 0.2s; (replace with 200ms) |  | 
| 285     transform: two .1s; (replace with 100ms)""") |  | 
| 286 |  | 
| 287   def testCssNoDataUrisInSourceFiles(self): |  | 
| 288     self.VerifyContentsProducesOutput(""" |  | 
| 289 img { |  | 
| 290   background: url( data:image/jpeg,4\/\/350|\/|3|2 ); |  | 
| 291 }""", """ |  | 
| 292 - Don't use data URIs in source files. Use grit instead. |  | 
| 293     background: url( data:image/jpeg,4\/\/350|\/|3|2 );""") |  | 
| 294 |  | 
| 295   def testCssNoMixinShims(self): |  | 
| 296     self.VerifyContentsProducesOutput(""" |  | 
| 297 :host { |  | 
| 298   --good-property: red; |  | 
| 299   --not-okay-mixin_-_not-okay-property: green; |  | 
| 300 }""", """ |  | 
| 301 - Don't override custom properties created by Polymer's mixin shim. Set \ |  | 
| 302 mixins or documented custom properties directly. |  | 
| 303     --not-okay-mixin_-_not-okay-property: green;""") |  | 
| 304 |  | 
| 305   def testCssNoQuotesInUrl(self): |  | 
| 306     self.VerifyContentsProducesOutput(""" |  | 
| 307 img { |  | 
| 308   background: url('chrome://resources/images/blah.jpg'); |  | 
| 309   background: url("../../folder/hello.png"); |  | 
| 310 }""", """ |  | 
| 311 - Use single quotes (') instead of double quotes (") in strings. |  | 
| 312     background: url("../../folder/hello.png"); |  | 
| 313 |  | 
| 314 - Don't use quotes in url(). |  | 
| 315     background: url('chrome://resources/images/blah.jpg'); |  | 
| 316     background: url("../../folder/hello.png");""") |  | 
| 317 |  | 
| 318   def testCssOneRulePerLine(self): |  | 
| 319     self.VerifyContentsProducesOutput(""" |  | 
| 320 a:not([hidden]):not(.custom-appearance):not([version=1]):first-of-type, |  | 
| 321 a:not([hidden]):not(.custom-appearance):not([version=1]):first-of-type ~ |  | 
| 322     input[type='checkbox']:not([hidden]), |  | 
| 323 div { |  | 
| 324   background: url(chrome://resources/BLAH); |  | 
| 325   rule: value; /* rule: value; */ |  | 
| 326   rule: value; rule: value; |  | 
| 327 } |  | 
| 328 |  | 
| 329 .remix { |  | 
| 330   --dj: { |  | 
| 331     spin: that; |  | 
| 332   }; |  | 
| 333 } |  | 
| 334 """, """ |  | 
| 335 - One rule per line (what not to do: color: red; margin: 0;). |  | 
| 336     rule: value; rule: value;""") |  | 
| 337 |  | 
| 338   def testCssOneSelectorPerLine(self): |  | 
| 339     self.VerifyContentsProducesOutput(""" |  | 
| 340 a, |  | 
| 341 div,a, |  | 
| 342 div,/* Hello! */ span, |  | 
| 343 #id.class([dir=rtl):not(.class):any(a, b, d) { |  | 
| 344   rule: value; |  | 
| 345 } |  | 
| 346 |  | 
| 347 a, |  | 
| 348 div,a { |  | 
| 349   some-other: rule here; |  | 
| 350 }""", """ |  | 
| 351 - One selector per line (what not to do: a, b {}). |  | 
| 352     div,a, |  | 
| 353     div, span, |  | 
| 354     div,a {""") |  | 
| 355 |  | 
| 356   def testCssPseudoElementDoubleColon(self): |  | 
| 357     self.VerifyContentsProducesOutput(""" |  | 
| 358 a:href, |  | 
| 359 br::after, |  | 
| 360 ::-webkit-scrollbar-thumb, |  | 
| 361 a:not([empty]):hover:focus:active, /* shouldn't catch here and above */ |  | 
| 362 abbr:after, |  | 
| 363 .tree-label:empty:after, |  | 
| 364 b:before, |  | 
| 365 :-WebKit-ScrollBar { |  | 
| 366   rule: value; |  | 
| 367 }""", """ |  | 
| 368 - Pseudo-elements should use double colon (i.e. ::after). |  | 
| 369     :after (should be ::after) |  | 
| 370     :after (should be ::after) |  | 
| 371     :before (should be ::before) |  | 
| 372     :-WebKit-ScrollBar (should be ::-WebKit-ScrollBar) |  | 
| 373     """) |  | 
| 374 |  | 
| 375   def testCssRgbIfNotGray(self): |  | 
| 376     self.VerifyContentsProducesOutput(""" |  | 
| 377 #abc, |  | 
| 378 #aaa, |  | 
| 379 #aabbcc { |  | 
| 380   background: -webkit-linear-gradient(left, from(#abc), to(#def)); |  | 
| 381   color: #bad; |  | 
| 382   color: #bada55; |  | 
| 383 }""", """ |  | 
| 384 - Use rgb() over #hex when not a shade of gray (like #333). |  | 
| 385     background: -webkit-linear-gradient(left, from(#abc), to(#def)); """ |  | 
| 386 """(replace with rgb(170, 187, 204), rgb(221, 238, 255)) |  | 
| 387     color: #bad; (replace with rgb(187, 170, 221)) |  | 
| 388     color: #bada55; (replace with rgb(186, 218, 85))""") |  | 
| 389 |  | 
| 390   def testWebkitBeforeOrAfter(self): |  | 
| 391     self.VerifyContentsProducesOutput(""" |  | 
| 392 .test { |  | 
| 393   -webkit-margin-before: 10px; |  | 
| 394   -webkit-margin-start: 20px; |  | 
| 395   -webkit-padding-after: 3px; |  | 
| 396   -webkit-padding-end: 5px; |  | 
| 397 } |  | 
| 398 """, """ |  | 
| 399 - Use *-top/bottom instead of -webkit-*-before/after. |  | 
| 400     -webkit-margin-before: 10px; (replace with margin-top) |  | 
| 401     -webkit-padding-after: 3px; (replace with padding-bottom)""") |  | 
| 402 |  | 
| 403   def testCssZeroWidthLengths(self): |  | 
| 404     self.VerifyContentsProducesOutput(""" |  | 
| 405 @-webkit-keyframe anim { |  | 
| 406   0% { /* Ignore key frames */ |  | 
| 407     width: 0px; |  | 
| 408   } |  | 
| 409   10% { |  | 
| 410     width: 10px; |  | 
| 411   } |  | 
| 412   100% { |  | 
| 413     width: 100px; |  | 
| 414   } |  | 
| 415 } |  | 
| 416 |  | 
| 417 #logo { |  | 
| 418   background-image: url(images/google_logo.png@2x); |  | 
| 419 } |  | 
| 420 |  | 
| 421 body.alternate-logo #logo { |  | 
| 422   -webkit-mask-image: url(images/google_logo.png@2x); |  | 
| 423 } |  | 
| 424 |  | 
| 425 /* http://crbug.com/359682 */ |  | 
| 426 #spinner-container #spinner { |  | 
| 427   -webkit-animation-duration: 1.0s; |  | 
| 428 } |  | 
| 429 |  | 
| 430 .media-button.play > .state0.active, |  | 
| 431 .media-button[state='0'] > .state0.normal /* blah */, /* blee */ |  | 
| 432 .media-button[state='0']:not(.disabled):hover > .state0.hover { |  | 
| 433   -webkit-animation: anim 0s; |  | 
| 434   -webkit-animation-duration: anim 0ms; |  | 
| 435   -webkit-transform: scale(0%); |  | 
| 436   background-position-x: 0em; |  | 
| 437   background-position-y: 0ex; |  | 
| 438   border-width: 0em; |  | 
| 439   color: hsl(0, 0%, 85%); /* Shouldn't trigger error. */ |  | 
| 440   opacity: .0; |  | 
| 441   opacity: 0.0; |  | 
| 442   opacity: 0.; |  | 
| 443 } |  | 
| 444 |  | 
| 445 @page { |  | 
| 446   border-width: 0mm; |  | 
| 447   height: 0cm; |  | 
| 448   width: 0in; |  | 
| 449 }""", """ |  | 
| 450 - Use "0" for zero-width lengths (i.e. 0px -> 0) |  | 
| 451     width: 0px; |  | 
| 452     -webkit-transform: scale(0%); |  | 
| 453     background-position-x: 0em; |  | 
| 454     background-position-y: 0ex; |  | 
| 455     border-width: 0em; |  | 
| 456     opacity: .0; |  | 
| 457     opacity: 0.0; |  | 
| 458     opacity: 0.; |  | 
| 459     border-width: 0mm; |  | 
| 460     height: 0cm; |  | 
| 461     width: 0in; |  | 
| 462 """) |  | 
| 463 |  | 
| 464   def testInlineStyleInHtml(self): |  | 
| 465     self.VerifyContentsProducesOutput("""<!doctype html> |  | 
| 466 <html> |  | 
| 467 <head> |  | 
| 468   <!-- Don't warn about problems outside of style tags |  | 
| 469     html, |  | 
| 470     body { |  | 
| 471       margin: 0; |  | 
| 472       height: 100%; |  | 
| 473     } |  | 
| 474   --> |  | 
| 475   <style> |  | 
| 476     body { |  | 
| 477       flex-direction:column; |  | 
| 478     } |  | 
| 479   </style> |  | 
| 480 </head> |  | 
| 481 </html>""", """ |  | 
| 482 - Colons (:) should have a space after them. |  | 
| 483     flex-direction:column; |  | 
| 484 """, filename='test.html') |  | 
| 485 |  | 
| 486   def testInlineStyleInHtmlWithIncludes(self): |  | 
| 487     self.VerifyContentsProducesOutput("""<!doctype html> |  | 
| 488 <html> |  | 
| 489   <style include="fake-shared-css other-shared-css"> |  | 
| 490     body { |  | 
| 491       flex-direction:column; |  | 
| 492     } |  | 
| 493   </style> |  | 
| 494 </head> |  | 
| 495 </html>""", """ |  | 
| 496 - Colons (:) should have a space after them. |  | 
| 497     flex-direction:column; |  | 
| 498 """, filename='test.html') |  | 
| 499 |  | 
| 500   def testInlineSTyleInHtmlWithTagsInComments(self): |  | 
| 501     self.VerifyContentsProducesOutput("""<!doctype html> |  | 
| 502 <html> |  | 
| 503   <style> |  | 
| 504     body { |  | 
| 505       /* You better ignore the <tag> in this comment! */ |  | 
| 506       flex-direction:column; |  | 
| 507     } |  | 
| 508   </style> |  | 
| 509 </head> |  | 
| 510 </html>""", """ |  | 
| 511 - Colons (:) should have a space after them. |  | 
| 512     flex-direction:column; |  | 
| 513 """, filename='test.html') |  | 
| 514 |  | 
| 515 |  | 
| 516 if __name__ == '__main__': |  | 
| 517   unittest.main() |  | 
| OLD | NEW | 
|---|