blob: a4d53df7573ed1f10e7b73fb9d44a6c44dcafc3d [file] [log] [blame]
// Copyright 2009 Google Inc. All Rights Reserved.
//
// 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.
#include "pagespeed/kernel/js/js_minify.h"
#include "pagespeed/kernel/base/google_message_handler.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/stdio_file_system.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/js/js_keywords.h"
namespace {
using net_instaweb::StrAppend;
// This sample code comes from Douglas Crockford's jsmin example.
const char* kBeforeCompilation =
"// is.js\n"
"\n"
"// (c) 2001 Douglas Crockford\n"
"// 2001 June 3\n"
"\n"
"\n"
"// is\n"
"\n"
"// The -is- object is used to identify the browser. "
"Every browser edition\n"
"// identifies itself, but there is no standard way of doing it, "
"and some of\n"
"// the identification is deceptive. This is because the authors of web\n"
"// browsers are liars. For example, Microsoft's IE browsers claim to be\n"
"// Mozilla 4. Netscape 6 claims to be version 5.\n"
"\n"
"var is = {\n"
" ie: navigator.appName == 'Microsoft Internet Explorer',\n"
" java: navigator.javaEnabled(),\n"
" ns: navigator.appName == 'Netscape',\n"
" ua: navigator.userAgent.toLowerCase(),\n"
" version: parseFloat(navigator.appVersion.substr(21)) ||\n"
" parseFloat(navigator.appVersion),\n"
" win: navigator.platform == 'Win32'\n"
"}\n"
"is.mac = is.ua.indexOf('mac') >= 0;\n"
"if (is.ua.indexOf('opera') >= 0) {\n"
" is.ie = is.ns = false;\n"
" is.opera = true;\n"
"}\n"
"if (is.ua.indexOf('gecko') >= 0) {\n"
" is.ie = is.ns = false;\n"
" is.gecko = true;\n"
"}\n";
const char* kAfterCompilationOld =
"var is={ie:navigator.appName=='Microsoft Internet Explorer',"
"java:navigator.javaEnabled(),ns:navigator.appName=='Netscape',"
"ua:navigator.userAgent.toLowerCase(),version:parseFloat("
"navigator.appVersion.substr(21))||parseFloat(navigator.appVersion)"
",win:navigator.platform=='Win32'}\n"
"is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){"
"is.ie=is.ns=false;is.opera=true;}\n"
"if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;}";
const char* kAfterCompilationNew =
"var is={ie:navigator.appName=='Microsoft Internet Explorer',"
"java:navigator.javaEnabled(),ns:navigator.appName=='Netscape',"
"ua:navigator.userAgent.toLowerCase(),version:parseFloat("
"navigator.appVersion.substr(21))||parseFloat(navigator.appVersion)"
",win:navigator.platform=='Win32'}\n"
"is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){"
"is.ie=is.ns=false;is.opera=true;}"
"if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;}";
const char kTestRootDir[] = "/pagespeed/kernel/js/testdata/third_party/";
class JsMinifyTest : public testing::Test {
protected:
void CheckOldMinification(StringPiece before, StringPiece after) {
GoogleString output;
EXPECT_TRUE(pagespeed::js::MinifyJs(before, &output));
EXPECT_EQ(after, output);
int output_size = -1;
EXPECT_TRUE(pagespeed::js::GetMinifiedJsSize(before, &output_size));
EXPECT_EQ(static_cast<int>(after.size()), output_size);
}
void CheckNewMinification(StringPiece before, StringPiece after) {
GoogleString output;
EXPECT_TRUE(pagespeed::js::MinifyUtf8Js(&patterns_, before, &output));
EXPECT_EQ(after, output);
}
void CheckMinification(StringPiece before, StringPiece after) {
CheckOldMinification(before, after);
CheckNewMinification(before, after);
}
void CheckOldError(StringPiece input) {
GoogleString output;
EXPECT_FALSE(pagespeed::js::MinifyJs(input, &output));
int output_size = -1;
EXPECT_FALSE(pagespeed::js::GetMinifiedJsSize(input, &output_size));
EXPECT_EQ(-1, output_size);
}
void CheckNewError(StringPiece input) {
GoogleString output;
EXPECT_FALSE(pagespeed::js::MinifyUtf8Js(&patterns_, input, &output));
}
void CheckError(StringPiece input) {
CheckOldError(input);
CheckNewError(input);
}
void CheckFileMinification(StringPiece before_filename,
StringPiece after_filename) {
net_instaweb::StdioFileSystem file_system;
net_instaweb::GoogleMessageHandler message_handler;
GoogleString original;
{
const GoogleString filepath = net_instaweb::StrCat(
net_instaweb::GTestSrcDir(), kTestRootDir, before_filename);
ASSERT_TRUE(file_system.ReadFile(
filepath.c_str(), &original, &message_handler));
}
GoogleString expected;
{
const GoogleString filepath = net_instaweb::StrCat(
net_instaweb::GTestSrcDir(), kTestRootDir, after_filename);
ASSERT_TRUE(file_system.ReadFile(
filepath.c_str(), &expected, &message_handler));
}
GoogleString actual;
EXPECT_TRUE(pagespeed::js::MinifyUtf8Js(&patterns_, original, &actual));
EXPECT_STREQ(expected, actual);
}
pagespeed::js::JsTokenizerPatterns patterns_;
};
TEST_F(JsMinifyTest, Basic) {
// Our new minifier is slightly better at removing linebreaks than our old
// minifier, so they get slightly different results for this test.
CheckOldMinification(kBeforeCompilation, kAfterCompilationOld);
CheckNewMinification(kBeforeCompilation, kAfterCompilationNew);
}
TEST_F(JsMinifyTest, AlreadyMinified) {
CheckMinification(kAfterCompilationNew, kAfterCompilationNew);
}
TEST_F(JsMinifyTest, Json) {
CheckMinification("{ \"foo\": {\"bar\": 1}, \"baz\": 2 } ",
"{\"foo\":{\"bar\":1},\"baz\":2}");
}
TEST_F(JsMinifyTest, ErrorUnclosedComment) {
CheckError("/* not valid javascript");
}
TEST_F(JsMinifyTest, ErrorUnclosedString) {
CheckError("\"not valid javascript");
}
TEST_F(JsMinifyTest, ErrorUnclosedRegex) {
CheckError("/not_valid_javascript");
}
TEST_F(JsMinifyTest, ErrorRegexNewline) {
CheckError("/not_valid\njavascript/;");
}
TEST_F(JsMinifyTest, SignedCharDoesntSignExtend) {
const unsigned char input[] = { 0xe0, 0xb2, 0xa0, 0x00 };
const char* input_nosign = reinterpret_cast<const char*>(input);
CheckMinification(input_nosign, input_nosign);
}
TEST_F(JsMinifyTest, DealWithCrlf) {
CheckMinification("var x = 1;\r\nvar y = 2;", "var x=1;var y=2;");
}
TEST_F(JsMinifyTest, DealWithTabs) {
CheckMinification("var x = 1;\n\tvar y = 2;", "var x=1;var y=2;");
}
TEST_F(JsMinifyTest, EscapedCrlfInStringLiteral) {
CheckMinification("var x = 'foo\\\r\nbar';", "var x='foo\\\r\nbar';");
}
TEST_F(JsMinifyTest, EmptyInput) {
CheckMinification("", "");
}
TEST_F(JsMinifyTest, TreatCarriageReturnAsLinebreak) {
CheckMinification("x = 1\ry = 2", "x=1\ny=2");
}
// See http://code.google.com/p/page-speed/issues/detail?id=607
TEST_F(JsMinifyTest, CarriageReturnEndsLineComment) {
CheckMinification("x = 1 // foobar\ry = 2", "x=1\ny=2");
}
// See http://code.google.com/p/page-speed/issues/detail?id=198
TEST_F(JsMinifyTest, LeaveIEConditionalCompilationComments) {
// Our new minifier is slightly better at removing linebreaks than our old
// minifier, so they get slightly different results for this test.
CheckOldMinification(
"/*@cc_on\n"
" /*@if (@_win32)\n"
" document.write('IE');\n"
" @else @*/\n"
" document.write('other');\n"
" /*@end\n"
"@*/",
"/*@cc_on\n"
" /*@if (@_win32)\n"
" document.write('IE');\n"
" @else @*/\n"
"document.write('other');/*@end\n"
"@*/");
CheckNewMinification(
"/*@cc_on\n"
" /*@if (@_win32)\n"
" document.write('IE');\n"
" @else @*/\n"
" document.write('other');\n"
" /*@end\n"
"@*/",
"/*@cc_on\n"
" /*@if (@_win32)\n"
" document.write('IE');\n"
" @else @*/document.write('other');/*@end\n"
"@*/");
}
TEST_F(JsMinifyTest, DoNotJoinPlusses) {
CheckMinification("var x = 'date=' + +new Date();",
"var x='date='+ +new Date();");
}
TEST_F(JsMinifyTest, DoNotJoinPlusAndPlusPlus) {
CheckMinification("var x = y + ++z;", "var x=y+ ++z;");
}
TEST_F(JsMinifyTest, DoNotJoinPlusPlusAndPlus) {
CheckMinification("var x = y++ + z;", "var x=y++ +z;");
}
TEST_F(JsMinifyTest, DoNotJoinMinuses) {
CheckMinification("var x = 'date=' - -new Date();",
"var x='date='- -new Date();");
}
TEST_F(JsMinifyTest, DoNotJoinMinusAndMinusMinus) {
CheckMinification("var x = y - --z;", "var x=y- --z;");
}
TEST_F(JsMinifyTest, DoNotJoinMinusMinusAndMinus) {
CheckMinification("var x = y-- - z;", "var x=y-- -z;");
}
TEST_F(JsMinifyTest, DoJoinBangs) {
CheckMinification("var x = ! ! y;", "var x=!!y;");
}
// See http://code.google.com/p/page-speed/issues/detail?id=242
TEST_F(JsMinifyTest, RemoveSurroundingSgmlComment) {
CheckMinification("<!--\nvar x = 42;\n//-->", "var x=42;");
}
TEST_F(JsMinifyTest, RemoveSurroundingSgmlCommentWithoutSlashSlash) {
CheckMinification("<!--\nvar x = 42;\n-->\n", "var x=42;");
}
// See http://code.google.com/p/page-speed/issues/detail?id=242
TEST_F(JsMinifyTest, SgmlLineComment) {
CheckMinification("var x = 42; <!-- comment\nvar y = 17;",
"var x=42;var y=17;");
}
TEST_F(JsMinifyTest, RemoveSgmlCommentCloseOnOwnLine1) {
CheckMinification("var x = 42;\n --> \n", "var x=42;");
}
TEST_F(JsMinifyTest, RemoveSgmlCommentCloseOnOwnLine2) {
CheckMinification("-->\nvar x = 42;\n", "var x=42;");
}
TEST_F(JsMinifyTest, DoNotRemoveSgmlCommentCloseInMidLine) {
CheckMinification("var x = 42; --> \n", "var x=42;-->");
}
TEST_F(JsMinifyTest, DoNotCreateLineComment) {
// Yes, this is legal code. It sets x to NaN.
CheckMinification("var x = 42 / /foo/;\n", "var x=42/ /foo/;");
}
TEST_F(JsMinifyTest, DoNotCreateSgmlLineComment1) {
// Yes, this is legal code. It tests if x is less than not(decrement y).
CheckMinification("if (x <! --y) { x = 0; }\n", "if(x<! --y){x=0;}");
}
TEST_F(JsMinifyTest, DoNotCreateSgmlLineComment2) {
// Yes, this is legal code. It tests if x is less than not(decrement y).
CheckMinification("if (x < !--y) { x = 0; }\n", "if(x< !--y){x=0;}");
}
TEST_F(JsMinifyTest, DoNotJoinDecimalIntegerAndDot) {
// 34 .toString() is legal code, but 34.toString() isn't, because the . in
// the second example gets parsed as part of the literal (decimal point). So
// we need to leave a space in there. Our old minifier gets this wrong, but
// the new minifier should handle it correctly.
CheckNewMinification("0192 . toString()", "0192 .toString()");
}
TEST_F(JsMinifyTest, DoJoinHexOctalIntegerAndDot) {
// On the other hand, hex and octal literals can't have decimal points, so we
// don't need the space here.
CheckMinification("0x3e2 . toString() + 0172 . toString()",
"0x3e2.toString()+0172.toString()");
}
TEST_F(JsMinifyTest, DoJoinDecimalFractionAndDot) {
// Also, if the decimal literal can't take another decimal point, then we can
// safely remove the space.
CheckMinification("3.5 . toString() + 3e2 . toString()",
"3.5.toString()+3e2.toString()");
}
TEST_F(JsMinifyTest, TrickyRegexLiteral) {
// The first assignment is two divisions; the second assignment is a regex
// literal. JSMin gets this wrong (it removes whitespace from the regex).
CheckMinification("var x = a[0] / b /i;\n var y = a[0] + / b /i;",
"var x=a[0]/b/i;var y=a[0]+/ b /i;");
}
TEST_F(JsMinifyTest, ObjectLiteralRegexLiteral) {
// On the first line, this looks like it should be an object literal divided
// by x divided by i, but nope, that's a block with a labelled expression
// statement, followed by a regex literal. The second line, on the other
// hand, _is_ an object literal, followed by division. Our old minifier gets
// the second one wrong, but the new minifier should handle it correctly.
CheckMinification("{foo: 123} / x /i;", "{foo:123}/ x /i;");
CheckNewMinification("x={foo: 1} / x /i;", "x={foo:1}/x/i;");
}
// See http://code.google.com/p/modpagespeed/issues/detail?id=327
TEST_F(JsMinifyTest, RegexLiteralWithBrackets1) {
// The / in [^/] doesn't end the regex, so the // is not a comment.
CheckMinification("var x = /http:\\/\\/[^/]+\\//, y = 3;",
"var x=/http:\\/\\/[^/]+\\//,y=3;");
}
TEST_F(JsMinifyTest, RegexLiteralWithBrackets2) {
// The first ] is escaped and doesn't close the [, so the following / doesn't
// close the regex, so the following space is still in the regex and must be
// preserved.
CheckMinification("var x = /z[\\]/ ]/, y = 3;",
"var x=/z[\\]/ ]/,y=3;");
}
TEST_F(JsMinifyTest, ReturnRegex1) {
// Make sure we understand that this is not division; "return" is not an
// identifier!
CheckMinification("return / x /g;", "return/ x /g;");
}
TEST_F(JsMinifyTest, ReturnRegex2) {
// This test comes from the real world. If "return" is incorrectly treated
// as an identifier, the second slash will be treated as opening a regex
// rather than closing it, and we'll error due to an unclosed regex.
CheckMinification("return/#.+/.test(\n'#24' );",
"return/#.+/.test('#24');");
}
TEST_F(JsMinifyTest, ThrowRegex) {
// Make sure we understand that this is not division; "throw" is not an
// identifier! (And yes, in JS you're allowed to throw a regex.)
CheckMinification("throw / x /g;", "throw/ x /g;");
}
TEST_F(JsMinifyTest, ReturnThrowNumber) {
CheckMinification("return 1;\nthrow 2;", "return 1;throw 2;");
}
TEST_F(JsMinifyTest, KeywordPrecedesRegex) {
// Make sure "typeof /./" sees the first "/" as a regex and not division.
// If it thinks it's a division then it will treat the "/ /" as a regex
// and not remove the comment. Do the same for all such keywords.
// Example, "typeof /./ /* hi there */;" -> "typeof/./;"
for (pagespeed::JsKeywords::Iterator iter; !iter.AtEnd(); iter.Next()) {
if (pagespeed::JsKeywords::CanKeywordPrecedeRegEx(iter.name())) {
GoogleString input =
net_instaweb::StrCat(iter.name(), " /./ /* hi there */;");
GoogleString expected = net_instaweb::StrCat(iter.name(), "/./;");
CheckMinification(input, expected);
}
}
}
TEST_F(JsMinifyTest, LoopRegex) {
// Make sure we understand that a slash after "while (...)" or "for (...)" is
// a regex, not division. Our old minifier gets this wrong, but the new
// minifier should handle it correctly.
CheckNewMinification("while (0) /\\//.exec('');",
"while(0)/\\//.exec('');");
CheckNewMinification("for (x in y) / z /.exec(x);",
"for(x in y)/ z /.exec(x);");
}
TEST_F(JsMinifyTest, LabelRegex) {
// Make sure we understand that a slash after a label is a regex, not
// division.
CheckMinification("{ foo: / x /.exec(''); }", "{foo:/ x /.exec('');}");
}
const char kCrashTestString[] =
"var x = 'asd \\' lse'\n"
"var y /*comment*/ = /regex/\n"
"var z = \"x =\" + x\n";
TEST_F(JsMinifyTest, DoNotCrash) {
// Run on all possible prefixes of kCrashTestString. We don't care about the
// result; we just want to make sure it doesn't crash.
for (int i = 0, size = sizeof(kCrashTestString); i <= size; ++i) {
GoogleString input(kCrashTestString, i);
GoogleString output;
pagespeed::js::MinifyJs(input, &output);
}
}
// The below tests check for some corner cases of semicolon insertion, to make
// sure that we are minifying as much as possible (and no more!).
// See http://inimino.org/~inimino/blog/javascript_semicolons for details.
TEST_F(JsMinifyTest, SemicolonInsertionIncrement) {
CheckMinification("a\n++b\nc++\nd", "a\n++b\nc++\nd");
// A trickier case that only the new minifier gets right:
CheckNewMinification("a\n++\nb\nc++\nd", "a\n++b\nc++\nd");
}
TEST_F(JsMinifyTest, SemicolonInsertionDecrement) {
CheckMinification("a\n--b\nc--\nd", "a\n--b\nc--\nd");
// A trickier case that only the new minifier gets right:
CheckNewMinification("a\n--\nb\nc--\nd", "a\n--b\nc--\nd");
}
TEST_F(JsMinifyTest, SemicolonInsertionAddition) {
// No semicolons will be inserted, so the linebreaks can be removed.
CheckMinification("i\n+\nj", "i+j");
}
TEST_F(JsMinifyTest, SemicolonInsertionSubtraction) {
// No semicolons will be inserted, so the linebreaks can be removed.
CheckMinification("i\n-\nj", "i-j");
}
TEST_F(JsMinifyTest, SemicolonInsertionLogicalOr) {
// No semicolons will be inserted, so the linebreaks can be removed.
CheckMinification("i\n||\nj", "i||j");
}
TEST_F(JsMinifyTest, SemicolonInsertionFuncCall) {
// No semicolons will be inserted, so the linebreak can be removed. This is
// actually a function call, not two statements.
CheckMinification("a = b + c\n(d + e).print()", "a=b+c(d+e).print()");
}
TEST_F(JsMinifyTest, SemicolonInsertionRegex) {
// No semicolon will be inserted, so the linebreak and spaces can be removed
// (this is two divisions, not a regex).
CheckMinification("i=0\n/ [a-z] /g.exec(s)", "i=0/[a-z]/g.exec(s)");
}
TEST_F(JsMinifyTest, SemicolonInsertionComment) {
CheckMinification("a=b\n /*hello*/ c=d\n", "a=b\nc=d");
}
TEST_F(JsMinifyTest, SemicolonInsertionWhileStmt) {
// No semicolon will be inserted, so the linebreak can be removed.
CheckMinification("while\n(true);", "while(true);");
}
TEST_F(JsMinifyTest, SemicolonInsertionReturnStmt1) {
// A semicolon _will_ be inserted, so the linebreak _cannot_ be removed.
CheckMinification("return\n(true);", "return\n(true);");
}
TEST_F(JsMinifyTest, SemicolonInsertionReturnStmt2) {
// A semicolon _will_ be inserted, so the linebreak _cannot_ be removed.
CheckMinification("return\n/*comment*/(true);", "return\n(true);");
}
TEST_F(JsMinifyTest, SemicolonInsertionThrowStmt) {
// This is _not_ legal code; don't accidentally make it legal by removing the
// linebreak. (Eliminating a syntax error would change the semantics!)
CheckMinification("throw\n 'error';", "throw\n'error';");
}
TEST_F(JsMinifyTest, SemicolonInsertionBreakStmt) {
// A semicolon _will_ be inserted, so the linebreak _cannot_ be removed.
CheckMinification("break\nlabel;", "break\nlabel;");
}
TEST_F(JsMinifyTest, SemicolonInsertionContinueStmt) {
// A semicolon _will_ be inserted, so the linebreak _cannot_ be removed.
CheckMinification("continue\nlabel;", "continue\nlabel;");
}
TEST_F(JsMinifyTest, SemicolonInsertionDebuggerStmt) {
// A semicolon _will_ be inserted, so the linebreak _cannot_ be removed.
CheckMinification("debugger\nfoo;", "debugger\nfoo;");
}
TEST_F(JsMinifyTest, Latin1Input) {
// Try to minify input that is Latin-1 encoded. This is not valid UTF-8, but
// we should be able to proceed gracefully (in most cases) if the non-ascii
// characters only ever appear in string literals and comments.
CheckMinification("str='Qu\xE9 pasa';// 'qu\xE9' means 'what'\n"
"cents=/* 73\xA2 is $0.73 */73;",
"str='Qu\xE9 pasa';cents=73;");
}
const char kCollapsingStringTestString[] =
"var x = 'asd \\' lse'\n"
"var y /*comment*/ = /re'gex/\n"
"var z = \"x =\" + x\n";
const char kCollapsedTestString[] =
"var x=''\n"
"var y=/re'gex/\n"
"var z=\"\"+x";
TEST_F(JsMinifyTest, CollapsingStringTest) {
int size = 0;
GoogleString output;
ASSERT_TRUE(pagespeed::js::MinifyJsAndCollapseStrings(
kCollapsingStringTestString, &output));
ASSERT_EQ(strlen(kCollapsedTestString), output.size());
ASSERT_EQ(kCollapsedTestString, output);
ASSERT_TRUE(pagespeed::js::GetMinifiedStringCollapsedJsSize(
kCollapsingStringTestString, &size));
ASSERT_EQ(static_cast<int>(strlen(kCollapsedTestString)), size);
}
TEST_F(JsMinifyTest, MinifyAngular) {
CheckFileMinification("angular.original", "angular.minified");
}
TEST_F(JsMinifyTest, MinifyJQuery) {
CheckFileMinification("jquery.original", "jquery.minified");
}
TEST_F(JsMinifyTest, MinifyPrototype) {
CheckFileMinification("prototype.original", "prototype.minified");
}
// Simple method for serializing Mappings so that they can be compared against
// gold versions.
GoogleString MappingsToString(
const net_instaweb::source_map::MappingVector& mappings) {
GoogleString result("{");
for (int i = 0, n = mappings.size(); i < n; ++i) {
StrAppend(&result, "(",
net_instaweb::IntegerToString(mappings[i].gen_line), ", ",
net_instaweb::IntegerToString(mappings[i].gen_col), ", ");
StrAppend(&result,
net_instaweb::IntegerToString(mappings[i].src_file), ", ",
net_instaweb::IntegerToString(mappings[i].src_line), ", ",
net_instaweb::IntegerToString(mappings[i].src_col), "), ");
}
result += "}";
return result;
}
TEST_F(JsMinifyTest, SourceMapsSimple) {
const char js_before[] =
"/* Simple hello world program. */\n"
"alert( 'Hello, World!' );\n";
const char expected_js_after[] =
"alert('Hello, World!');";
const char expected_map[] =
"{"
"(0, 0, 0, 1, 0), " // alert(
"(0, 6, 0, 1, 7), " // 'Hello, World!'
"(0, 21, 0, 1, 23), " // );
"}";
GoogleString output;
net_instaweb::source_map::MappingVector mappings;
EXPECT_TRUE(pagespeed::js::MinifyUtf8JsWithSourceMap(
&patterns_, js_before, &output, &mappings));
EXPECT_EQ(expected_js_after, output);
EXPECT_EQ(expected_map, MappingsToString(mappings));
}
TEST_F(JsMinifyTest, SourceMapsComplex) {
GoogleString output;
net_instaweb::source_map::MappingVector mappings;
EXPECT_TRUE(pagespeed::js::MinifyUtf8JsWithSourceMap(
&patterns_, kBeforeCompilation, &output, &mappings));
EXPECT_EQ(kAfterCompilationNew, output);
const char expected_map[] =
"{"
"(0, 0, 0, 14, 0), " // var is
"(0, 6, 0, 14, 7), " // =
"(0, 7, 0, 14, 9), " // {
"(0, 8, 0, 15, 4), " // ie:
"(0, 11, 0, 15, 13), " // navigator.appName
"(0, 28, 0, 15, 31), " // ==
"(0, 30, 0, 15, 34), " // 'Microsoft Internet Explorer',
"(0, 60, 0, 16, 4), " // java:
"(0, 65, 0, 16, 13), " // navigator.javaEnabled(),
"(0, 89, 0, 17, 4), " // ns:
"(0, 92, 0, 17, 13), " // navigator.appName
"(0, 109, 0, 17, 31), " // ==
"(0, 111, 0, 17, 34), " // 'Netscape',
"(0, 122, 0, 18, 4), " // ua:
"(0, 125, 0, 18, 13), " // navigator.userAgent.toLowerCase(),
"(0, 159, 0, 19, 4), " // version:
"(0, 167, 0, 19, 13), " // parseFloat(navigator.appVersion.substr(21))
"(0, 210, 0, 19, 57), " // ||
"(0, 212, 0, 20, 13), " // parseFloat(navigator.appVersion),
"(0, 245, 0, 21, 4), " // win:
"(0, 249, 0, 21, 13), " // navigator.platform
"(0, 267, 0, 21, 32), " // ==
"(0, 269, 0, 21, 35), " // 'Win32'
"(0, 276, 0, 22, 0), " // }
"(1, 0, 0, 23, 0), " // is.mac
"(1, 6, 0, 23, 7), " // =
"(1, 7, 0, 23, 9), " // is.ua.indexOf('mac')
"(1, 27, 0, 23, 30), " // >=
"(1, 29, 0, 23, 33), " // 0;
"(1, 31, 0, 24, 0), " // if
"(1, 33, 0, 24, 3), " // (is.ua.indexOf('opera')
"(1, 56, 0, 24, 27), " // >=
"(1, 58, 0, 24, 30), " // 0)
"(1, 60, 0, 24, 33), " // {
"(1, 61, 0, 25, 4), " // is.ie
"(1, 66, 0, 25, 10), " // =
"(1, 67, 0, 25, 12), " // is.ns
"(1, 72, 0, 25, 18), " // =
"(1, 73, 0, 25, 20), " // false;
"(1, 79, 0, 26, 4), " // is.opera
"(1, 87, 0, 26, 13), " // =
"(1, 88, 0, 26, 15), " // true;
"(1, 93, 0, 27, 0), " // }
"(1, 94, 0, 28, 0), " // if
"(1, 96, 0, 28, 3), " // (is.ua.indexOf('gecko')
"(1, 119, 0, 28, 27), " // >=
"(1, 121, 0, 28, 30), " // 0)
"(1, 123, 0, 28, 33), " // {
"(1, 124, 0, 29, 4), " // is.ie
"(1, 129, 0, 29, 10), " // =
"(1, 130, 0, 29, 12), " // is.ns
"(1, 135, 0, 29, 18), " // =
"(1, 136, 0, 29, 20), " // false;
"(1, 142, 0, 30, 4), " // is.gecko
"(1, 150, 0, 30, 13), " // =
"(1, 151, 0, 30, 15), " // true;
"(1, 156, 0, 31, 0), " // }
"}";
EXPECT_EQ(expected_map, MappingsToString(mappings));
}
} // namespace