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 |