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, '..', '..', '..', 'tools')) |
| 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_name = 'fake.css' |
| 24 |
| 25 self.fake_file = self.mox.CreateMockAnything() |
| 26 self.mox.StubOutWithMock(self.fake_file, 'LocalPath') |
| 27 self.fake_file.LocalPath().AndReturn(self.fake_file_name) |
| 28 # Actual calls to NewContents() are defined in each test. |
| 29 self.mox.StubOutWithMock(self.fake_file, 'NewContents') |
| 30 |
| 31 self.input_api = self.mox.CreateMockAnything() |
| 32 self.input_api.re = re |
| 33 self.mox.StubOutWithMock(self.input_api, 'AffectedSourceFiles') |
| 34 self.input_api.AffectedFiles( |
| 35 include_deletes=False, file_filter=None).AndReturn([self.fake_file]) |
| 36 |
| 37 # Actual creations of PresubmitPromptWarning are defined in each test. |
| 38 self.output_api = self.mox.CreateMockAnything() |
| 39 self.mox.StubOutWithMock(self.output_api, 'PresubmitPromptWarning', |
| 40 use_mock_anything=True) |
| 41 |
| 42 self.output_api = self.mox.CreateMockAnything() |
| 43 self.mox.StubOutWithMock(self.output_api, 'PresubmitNotifyResult', |
| 44 use_mock_anything=True) |
| 45 |
| 46 def VerifyContentsIsValid(self, contents): |
| 47 self.fake_file.NewContents().AndReturn(contents.splitlines()) |
| 48 self.mox.ReplayAll() |
| 49 css_checker.CSSChecker(self.input_api, self.output_api).RunChecks() |
| 50 |
| 51 def VerifyContentsProducesOutput(self, contents, output): |
| 52 self.fake_file.NewContents().AndReturn(contents.splitlines()) |
| 53 author_msg = ('Was the CSS checker useful? ' |
| 54 'Send feedback or hate mail to dbeam@chromium.org.') |
| 55 self.output_api.PresubmitNotifyResult(author_msg).AndReturn(None) |
| 56 self.output_api.PresubmitPromptWarning( |
| 57 self.fake_file_name + ':\n' + output.strip()).AndReturn(None) |
| 58 self.mox.ReplayAll() |
| 59 css_checker.CSSChecker(self.input_api, self.output_api).RunChecks() |
| 60 |
| 61 def testCssAlphaWithAtBlock(self): |
| 62 self.VerifyContentsProducesOutput(""" |
| 63 <include src="../shared/css/cr/ui/overlay.css"> |
| 64 <include src="chrome://resources/totally-cool.css" /> |
| 65 |
| 66 /* A hopefully safely ignored comment and @media statement. /**/ |
| 67 @media print { |
| 68 div { |
| 69 display: block; |
| 70 color: red; |
| 71 } |
| 72 } |
| 73 |
| 74 .rule { |
| 75 z-index: 5; |
| 76 <if expr="not is macosx"> |
| 77 background-image: url(chrome://resources/BLAH); /* TODO(dbeam): Fix this. */ |
| 78 background-color: rgb(235, 239, 249); |
| 79 </if> |
| 80 <if expr="is_macosx"> |
| 81 background-color: white; |
| 82 background-image: url(chrome://resources/BLAH2); |
| 83 </if> |
| 84 color: black; |
| 85 } |
| 86 |
| 87 <if expr="is_macosx"> |
| 88 .language-options-right { |
| 89 visibility: hidden; |
| 90 opacity: 1; /* TODO(dbeam): Fix this. */ |
| 91 } |
| 92 </if>""", """ |
| 93 - Alphabetize properties and list vendor specific (i.e. -webkit) above standard. |
| 94 display: block; |
| 95 color: red; |
| 96 |
| 97 z-index: 5; |
| 98 color: black;""") |
| 99 |
| 100 def testCssStringWithAt(self): |
| 101 self.VerifyContentsIsValid(""" |
| 102 #logo { |
| 103 background-image: url(images/google_logo.png@2x); |
| 104 } |
| 105 |
| 106 body.alternate-logo #logo { |
| 107 -webkit-mask-image: url(images/google_logo.png@2x); |
| 108 background: none; |
| 109 } |
| 110 |
| 111 .stuff1 { |
| 112 } |
| 113 |
| 114 .stuff2 { |
| 115 } |
| 116 """) |
| 117 |
| 118 def testCssAlphaWithNonStandard(self): |
| 119 self.VerifyContentsProducesOutput(""" |
| 120 div { |
| 121 /* A hopefully safely ignored comment and @media statement. /**/ |
| 122 color: red; |
| 123 -webkit-margin-start: 5px; |
| 124 }""", """ |
| 125 - Alphabetize properties and list vendor specific (i.e. -webkit) above standard. |
| 126 color: red; |
| 127 -webkit-margin-start: 5px;""") |
| 128 |
| 129 def testCssAlphaWithLongerDashedProps(self): |
| 130 self.VerifyContentsProducesOutput(""" |
| 131 div { |
| 132 border-left: 5px; /* A hopefully removed comment. */ |
| 133 border: 5px solid red; |
| 134 }""", """ |
| 135 - Alphabetize properties and list vendor specific (i.e. -webkit) above standard. |
| 136 border-left: 5px; |
| 137 border: 5px solid red;""") |
| 138 |
| 139 def testCssBracesHaveSpaceBeforeAndNothingAfter(self): |
| 140 self.VerifyContentsProducesOutput(""" |
| 141 /* Hello! */div/* Comment here*/{ |
| 142 display: block; |
| 143 } |
| 144 |
| 145 blah /* hey! */ |
| 146 { |
| 147 rule: value; |
| 148 } |
| 149 |
| 150 .this.is { /* allowed */ |
| 151 rule: value; |
| 152 }""", """ |
| 153 - Start braces ({) end a selector, have a space before them and no rules after. |
| 154 div{ |
| 155 {""") |
| 156 |
| 157 def testCssClassesUseDashes(self): |
| 158 self.VerifyContentsProducesOutput(""" |
| 159 .className, |
| 160 .ClassName, |
| 161 .class-name /* We should not catch this. */, |
| 162 .class_name { |
| 163 display: block; |
| 164 }""", """ |
| 165 - Classes use .dash-form. |
| 166 .className, |
| 167 .ClassName, |
| 168 .class_name {""") |
| 169 |
| 170 def testCssCloseBraceOnNewLine(self): |
| 171 self.VerifyContentsProducesOutput(""" |
| 172 @media { /* TODO(dbeam) Fix this case. */ |
| 173 .rule { |
| 174 display: block; |
| 175 }} |
| 176 |
| 177 @-webkit-keyframe blah { |
| 178 from { height: rotate(-10turn); } |
| 179 100% { height: 500px; } |
| 180 } |
| 181 |
| 182 #id { /* ${TemplateExpressions} should be ignored. */ |
| 183 rule: ${someValue}; |
| 184 --css-mixin: { |
| 185 color: red; |
| 186 }; |
| 187 } |
| 188 |
| 189 #rule { |
| 190 rule: value; }""", """ |
| 191 - Always put a rule closing brace (}) on a new line. |
| 192 rule: value; }""") |
| 193 |
| 194 def testCssColonsHaveSpaceAfter(self): |
| 195 self.VerifyContentsProducesOutput(""" |
| 196 div:not(.class):not([attr=5]), /* We should not catch this. */ |
| 197 div:not(.class):not([attr]) /* Nor this. */ { |
| 198 background: url(data:image/jpeg,asdfasdfsadf); /* Ignore this. */ |
| 199 background: -webkit-linear-gradient(left, red, |
| 200 80% blah blee blar); |
| 201 color: red; |
| 202 display:block; |
| 203 }""", """ |
| 204 - Colons (:) should have a space after them. |
| 205 display:block; |
| 206 |
| 207 - Don't use data URIs in source files. Use grit instead. |
| 208 background: url(data:image/jpeg,asdfasdfsadf);""") |
| 209 |
| 210 def testCssFavorSingleQuotes(self): |
| 211 self.VerifyContentsProducesOutput(""" |
| 212 html[dir="rtl"] body, |
| 213 html[dir=ltr] body /* TODO(dbeam): Require '' around rtl in future? */ { |
| 214 font-family: "Open Sans"; |
| 215 <if expr="is_macosx"> |
| 216 blah: blee; |
| 217 </if> |
| 218 }""", """ |
| 219 - Use single quotes (') instead of double quotes (") in strings. |
| 220 html[dir="rtl"] body, |
| 221 font-family: "Open Sans";""") |
| 222 |
| 223 def testCssHexCouldBeShorter(self): |
| 224 self.VerifyContentsProducesOutput(""" |
| 225 #abc, |
| 226 #abc-, |
| 227 #abc-ghij, |
| 228 #abcdef-, |
| 229 #abcdef-ghij, |
| 230 #aaaaaa, |
| 231 #bbaacc { |
| 232 background-color: #336699; /* Ignore short hex rule if not gray. */ |
| 233 color: #999999; |
| 234 color: #666; |
| 235 }""", """ |
| 236 - Use abbreviated hex (#rgb) when in form #rrggbb. |
| 237 color: #999999; (replace with #999) |
| 238 |
| 239 - Use rgb() over #hex when not a shade of gray (like #333). |
| 240 background-color: #336699; (replace with rgb(51, 102, 153))""") |
| 241 |
| 242 def testCssUseMillisecondsForSmallTimes(self): |
| 243 self.VerifyContentsProducesOutput(""" |
| 244 .transition-0s /* This is gross but may happen. */ { |
| 245 transform: one 0.2s; |
| 246 transform: two .1s; |
| 247 transform: tree 1s; |
| 248 transform: four 300ms; |
| 249 }""", """ |
| 250 - Use milliseconds for time measurements under 1 second. |
| 251 transform: one 0.2s; (replace with 200ms) |
| 252 transform: two .1s; (replace with 100ms)""") |
| 253 |
| 254 def testCssNoDataUrisInSourceFiles(self): |
| 255 self.VerifyContentsProducesOutput(""" |
| 256 img { |
| 257 background: url( data:image/jpeg,4\/\/350|\/|3|2 ); |
| 258 }""", """ |
| 259 - Don't use data URIs in source files. Use grit instead. |
| 260 background: url( data:image/jpeg,4\/\/350|\/|3|2 );""") |
| 261 |
| 262 def testCssNoQuotesInUrl(self): |
| 263 self.VerifyContentsProducesOutput(""" |
| 264 img { |
| 265 background: url('chrome://resources/images/blah.jpg'); |
| 266 background: url("../../folder/hello.png"); |
| 267 }""", """ |
| 268 - Use single quotes (') instead of double quotes (") in strings. |
| 269 background: url("../../folder/hello.png"); |
| 270 |
| 271 - Don't use quotes in url(). |
| 272 background: url('chrome://resources/images/blah.jpg'); |
| 273 background: url("../../folder/hello.png");""") |
| 274 |
| 275 def testCssOneRulePerLine(self): |
| 276 self.VerifyContentsProducesOutput(""" |
| 277 a:not([hidden]):not(.custom-appearance):not([version=1]):first-of-type, |
| 278 a:not([hidden]):not(.custom-appearance):not([version=1]):first-of-type ~ |
| 279 input[type='checkbox']:not([hidden]), |
| 280 div { |
| 281 background: url(chrome://resources/BLAH); |
| 282 rule: value; /* rule: value; */ |
| 283 rule: value; rule: value; |
| 284 }""", """ |
| 285 - One rule per line (what not to do: color: red; margin: 0;). |
| 286 rule: value; rule: value;""") |
| 287 |
| 288 def testCssOneSelectorPerLine(self): |
| 289 self.VerifyContentsProducesOutput(""" |
| 290 a, |
| 291 div,a, |
| 292 div,/* Hello! */ span, |
| 293 #id.class([dir=rtl):not(.class):any(a, b, d) { |
| 294 rule: value; |
| 295 } |
| 296 |
| 297 a, |
| 298 div,a { |
| 299 some-other: rule here; |
| 300 }""", """ |
| 301 - One selector per line (what not to do: a, b {}). |
| 302 div,a, |
| 303 div, span, |
| 304 div,a {""") |
| 305 |
| 306 def testCssPseudoElementDoubleColon(self): |
| 307 self.VerifyContentsProducesOutput(""" |
| 308 a:href, |
| 309 br::after, |
| 310 ::-webkit-scrollbar-thumb, |
| 311 a:not([empty]):hover:focus:active, /* shouldn't catch here and above */ |
| 312 abbr:after, |
| 313 .tree-label:empty:after, |
| 314 b:before, |
| 315 :-WebKit-ScrollBar { |
| 316 rule: value; |
| 317 }""", """ |
| 318 - Pseudo-elements should use double colon (i.e. ::after). |
| 319 :after (should be ::after) |
| 320 :after (should be ::after) |
| 321 :before (should be ::before) |
| 322 :-WebKit-ScrollBar (should be ::-WebKit-ScrollBar) |
| 323 """) |
| 324 |
| 325 def testCssRgbIfNotGray(self): |
| 326 self.VerifyContentsProducesOutput(""" |
| 327 #abc, |
| 328 #aaa, |
| 329 #aabbcc { |
| 330 background: -webkit-linear-gradient(left, from(#abc), to(#def)); |
| 331 color: #bad; |
| 332 color: #bada55; |
| 333 }""", """ |
| 334 - Use rgb() over #hex when not a shade of gray (like #333). |
| 335 background: -webkit-linear-gradient(left, from(#abc), to(#def)); """ |
| 336 """(replace with rgb(170, 187, 204), rgb(221, 238, 255)) |
| 337 color: #bad; (replace with rgb(187, 170, 221)) |
| 338 color: #bada55; (replace with rgb(186, 218, 85))""") |
| 339 |
| 340 def testWebkitBeforeOrAfter(self): |
| 341 self.VerifyContentsProducesOutput(""" |
| 342 .test { |
| 343 -webkit-margin-before: 10px; |
| 344 -webkit-margin-start: 20px; |
| 345 -webkit-padding-after: 3px; |
| 346 -webkit-padding-end: 5px; |
| 347 } |
| 348 """, """ |
| 349 - Use *-top/bottom instead of -webkit-*-before/after. |
| 350 -webkit-margin-before: 10px; (replace with margin-top) |
| 351 -webkit-padding-after: 3px; (replace with padding-bottom)""") |
| 352 |
| 353 def testCssZeroWidthLengths(self): |
| 354 self.VerifyContentsProducesOutput(""" |
| 355 @-webkit-keyframe anim { |
| 356 0% { /* Ignore key frames */ |
| 357 width: 0px; |
| 358 } |
| 359 10% { |
| 360 width: 10px; |
| 361 } |
| 362 100% { |
| 363 width: 100px; |
| 364 } |
| 365 } |
| 366 |
| 367 #logo { |
| 368 background-image: url(images/google_logo.png@2x); |
| 369 } |
| 370 |
| 371 body.alternate-logo #logo { |
| 372 -webkit-mask-image: url(images/google_logo.png@2x); |
| 373 } |
| 374 |
| 375 /* http://crbug.com/359682 */ |
| 376 #spinner-container #spinner { |
| 377 -webkit-animation-duration: 1.0s; |
| 378 } |
| 379 |
| 380 .media-button.play > .state0.active, |
| 381 .media-button[state='0'] > .state0.normal /* blah */, /* blee */ |
| 382 .media-button[state='0']:not(.disabled):hover > .state0.hover { |
| 383 -webkit-animation: anim 0s; |
| 384 -webkit-animation-duration: anim 0ms; |
| 385 -webkit-transform: scale(0%); |
| 386 background-position-x: 0em; |
| 387 background-position-y: 0ex; |
| 388 border-width: 0em; |
| 389 color: hsl(0, 0%, 85%); /* Shouldn't trigger error. */ |
| 390 opacity: .0; |
| 391 opacity: 0.0; |
| 392 opacity: 0.; |
| 393 } |
| 394 |
| 395 @page { |
| 396 border-width: 0mm; |
| 397 height: 0cm; |
| 398 width: 0in; |
| 399 }""", """ |
| 400 - Use "0" for zero-width lengths (i.e. 0px -> 0) |
| 401 width: 0px; |
| 402 -webkit-transform: scale(0%); |
| 403 background-position-x: 0em; |
| 404 background-position-y: 0ex; |
| 405 border-width: 0em; |
| 406 opacity: .0; |
| 407 opacity: 0.0; |
| 408 opacity: 0.; |
| 409 border-width: 0mm; |
| 410 height: 0cm; |
| 411 width: 0in; |
| 412 """) |
| 413 |
| 414 |
| 415 if __name__ == '__main__': |
| 416 unittest.main() |
OLD | NEW |