| /** |
| * Copyright 2010 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // Author: jmarantz@google.com (Joshua Marantz) |
| // and sligocki@google.com (Shawn Ligocki) |
| |
| // Unit-test the html rewriter |
| #include "base/scoped_ptr.h" |
| #include "net/instaweb/rewriter/public/css_filter.h" |
| #include "net/instaweb/rewriter/public/css_move_to_head_filter.h" |
| #include "net/instaweb/rewriter/public/css_tag_scanner.h" |
| #include "net/instaweb/rewriter/public/img_tag_scanner.h" |
| #include "net/instaweb/rewriter/public/resource_manager.h" |
| #include "net/instaweb/rewriter/public/resource_manager_test_base.h" |
| #include <string> |
| |
| namespace net_instaweb { |
| |
| class CssFilterTest : public ResourceManagerTestBase { |
| protected: |
| |
| |
| // Check that inline CSS get's rewritten correctly. |
| void ValidateRewriteInlineCss(const char* id, |
| const StringPiece& css_input, |
| const StringPiece& css_expected_output) { |
| rewrite_driver_.AddFilter(RewriteOptions::kRewriteCss); |
| |
| static const char prefix[] = |
| "<head>\n" |
| " <title>Example style outline</title>\n" |
| " <!-- Style starts here -->\n" |
| " <style type='text/css'>"; |
| static const char suffix[] = "</style>\n" |
| " <!-- Style ends here -->\n" |
| "</head>"; |
| |
| std::string html_input = StrCat(prefix, css_input, suffix); |
| std::string html_output = StrCat(prefix, css_expected_output, suffix); |
| |
| ValidateExpected(id, html_input, html_output); |
| } |
| |
| void ValidateNoChangeRewriteInlineCss(const char* id, |
| const StringPiece& css_input) { |
| ValidateRewriteInlineCss(id, css_input, css_input); |
| } |
| }; |
| |
| |
| TEST_F(CssFilterTest, SimpleRewriteCssTest) { |
| std::string input_style = |
| "background_blue { background-color: #f00; }\n" |
| "foreground_yellow { color: yellow; }\n"; |
| std::string output_style = |
| "background_blue{background-color:red}" |
| "foreground_yellow{color:#ff0}"; |
| |
| ValidateRewriteInlineCss("rewrite_css", input_style, output_style); |
| } |
| |
| // Make sure we can deal with 0 character nodes between open and close of style. |
| TEST_F(CssFilterTest, RewriteEmptyCssTest) { |
| ValidateRewriteInlineCss("rewrite_empty_css", "", ""); |
| } |
| |
| // Make sure we don't change CSS with errors. Note: We can move these tests |
| // to expected rewrites if we find safe ways to edit them. |
| TEST_F(CssFilterTest, NoRewriteError) { |
| ValidateNoChangeRewriteInlineCss("non_unicode_charset", |
| "a { font-family: \"\xCB\xCE\xCC\xE5\"; }"); |
| // From http://www.baidu.com/ |
| ValidateNoChangeRewriteInlineCss("non_unicode_baidu", |
| "#lk span {font:14px \"\xCB\xCE\xCC\xE5\"}"); |
| // From http://www.yahoo.com/ |
| const char confusing_value[] = |
| "a { background-image:-webkit-gradient(linear, 50% 0%, 50% 100%," |
| " from(rgb(232, 237, 240)), to(rgb(252, 252, 253)));}"; |
| ValidateNoChangeRewriteInlineCss("non_standard_value", confusing_value); |
| |
| ValidateNoChangeRewriteInlineCss("bad_char_in_selector", |
| ".bold: { font-weight: bold }"); |
| } |
| |
| TEST_F(CssFilterTest, RewriteVariousCss) { |
| // Distilled examples. |
| const char* examples[] = { |
| "a.b #c.d e#d,f:g>h+i>j{color:red}", // .#,>+: in selectors |
| "a{border:solid 1px #ccc}", // Multiple values declaration |
| "a{border:none!important}", // !important |
| "a{background-image:url(foo.png)}", // url |
| "a{background-position:-19px 60%}", // negative position |
| "a{margin:0}", // 0 w/ no units |
| "a{padding:0.01em 0.25em}", // fractions and em |
| "a{-moz-border-radius-topleft:0}", // Browser-specific (-moz) |
| "a{background:none}", // CSS Parser used to expand this. |
| }; |
| for (int i = 0; i < arraysize(examples); ++i) { |
| ValidateNoChangeRewriteInlineCss("distilled_css", examples[i]); |
| } |
| } |
| |
| |
| // Test more complicated CSS. |
| TEST_F(CssFilterTest, ComplexCssTest) { |
| // Real-world examples. Picked out of Wikipedia's CSS. |
| const char* examples[][2] = { |
| { "#userlogin, #userloginForm {\n" |
| " border: solid 1px #cccccc;\n" |
| " padding: 1.2em;\n" |
| " float: left;\n" |
| "}\n", |
| |
| "#userlogin,#userloginForm{border:solid 1px #ccc;padding:1.2em;" |
| "float:left}"}, |
| |
| { "h3 .editsection { font-size: 76%; font-weight: normal; }\n", |
| "h3 .editsection{font-size:76%;font-weight:normal}"}, |
| |
| { "div.magnify a, div.magnify img {\n" |
| " display: block;\n" |
| " border: none !important;\n" |
| " background: none !important;\n" |
| "}\n", |
| |
| "div.magnify a,div.magnify img{display:block;border:none!important;" |
| "background:none!important}"}, |
| |
| { "#ca-watch.icon a:hover {\n" |
| " background-image: url('images/watch-icons.png?1');\n" |
| " background-position: -19px 60%;\n" |
| "}\n", |
| |
| "#ca-watch.icon a:hover{background-image:url(images/watch-icons.png?1);" |
| "background-position:-19px 60%}"}, |
| |
| { "body {\n" |
| " background: White;\n" |
| " /*font-size: 11pt !important;*/\n" |
| " color: Black;\n" |
| " margin: 0;\n" |
| " padding: 0;\n" |
| "}\n", |
| |
| "body{background:#fff;color:#000;margin:0;padding:0}"}, |
| |
| { ".suggestions-result{\n" |
| " color:black;\n" |
| " color:WindowText;\n" |
| " padding:0.01em 0.25em;\n" |
| "}\n", |
| |
| // TODO(sligocki): Do we care about color:WindowText? |
| //".suggestions-result{color:#000;color:WindowText;padding:0.01em 0.25em}" |
| |
| ".suggestions-result{color:#000;color:#000;padding:0.01em 0.25em}"}, |
| |
| { ".ui-corner-tl { -moz-border-radius-topleft: 0; -webkit-border-top-left" |
| "-radius: 0; }\n", |
| |
| ".ui-corner-tl{-moz-border-radius-topleft:0;-webkit-border-top-left" |
| "-radius:0}"}, |
| |
| { ".ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li." |
| "ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { " |
| "cursor: pointer; }\n", |
| |
| ".ui-tabs .ui-tabs-nav li.ui-tabs-selected a,.ui-tabs .ui-tabs-nav " |
| "li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-state-processing a{" |
| "cursor:pointer}"}, |
| |
| { ".ui-datepicker-cover {\n" |
| " display: none; /*sorry for IE5*/\n" |
| " display/**/: block; /*sorry for IE5*/\n" |
| " position: absolute; /*must have*/\n" |
| " z-index: -1; /*must have*/\n" |
| " filter: mask(); /*must have*/\n" |
| " top: -4px; /*must have*/\n" |
| " left: -4px; /*must have*/\n" |
| " width: 200px; /*must have*/\n" |
| " height: 200px; /*must have*/\n" |
| "}\n", |
| |
| // TODO(sligocki): Do we care about stripping /**/? |
| // Comment says it's for IE5. |
| //".ui-datepicker-cover{display:none;display/**/:block;position:absolute;" |
| //"z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px}" |
| |
| ".ui-datepicker-cover{display:none;display:block;position:absolute;" |
| "z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px}"} |
| }; |
| |
| for (int i = 0; i < arraysize(examples); ++i) { |
| ValidateRewriteInlineCss("complex_css", examples[i][0], examples[i][1]); |
| } |
| } |
| |
| } // namespace net_instaweb |