blob: 95bbaa68ddf5df3a776ab5db871c7d80f02e507d [file] [log] [blame]
/**
* 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.
*/
// Copyright 2006 Google Inc. All Rights Reserved.
// Author: dpeng@google.com (Daniel Peng)
#include "webutil/css/parser.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/scoped_ptr.h"
#include "testing/base/public/googletest.h"
#include "testing/base/public/gunit.h"
namespace Css {
class ParserTest : public testing::Test {
protected:
// The various Test* functions below check that parselen characters
// are parsed. Pass -1 to indicate that the entire string should be
// parsed.
// Checks that unescaping s returns value.
void TestUnescape(const char* s, int parselen, char32 value) {
SCOPED_TRACE(s);
Parser a(s);
if (parselen == -1) parselen = strlen(s);
EXPECT_EQ(value, a.ParseEscape());
EXPECT_EQ(parselen, a.getpos() - s);
}
// Checks that ParseIdent(s) returns utf8golden.
void TestIdent(const char* s, int parselen, string utf8golden) {
SCOPED_TRACE(s);
Parser a(s);
if (parselen == -1) parselen = strlen(s);
EXPECT_EQ(utf8golden, UnicodeTextToUTF8(a.ParseIdent()));
EXPECT_EQ(parselen, a.getpos() - s);
}
// Checks that ParseString<">(s) returns utf8golden.
void TestDstring(const char* s, int parselen, string utf8golden) {
SCOPED_TRACE(s);
Parser a(s);
if (parselen == -1) parselen = strlen(s);
EXPECT_EQ(utf8golden, UnicodeTextToUTF8(a.ParseString<'"'>()));
EXPECT_EQ(parselen, a.getpos() - s);
}
// Checks that ParseString<'>(s) returns utf8golden.
void TestSstring(const char* s, int parselen, string utf8golden) {
SCOPED_TRACE(s);
Parser a(s);
if (parselen == -1) parselen = strlen(s);
EXPECT_EQ(utf8golden, UnicodeTextToUTF8(a.ParseString<'\''>()));
EXPECT_EQ(parselen, a.getpos() - s);
}
// Checks that ParseAny(s) returns goldennum with goldenunit unit.
void TestAnyNum(const char* s, int parselen, float goldennum,
Value::Unit goldenunit) {
SCOPED_TRACE(s);
Parser a(s);
if (parselen == -1) parselen = strlen(s);
scoped_ptr<Value> t(a.ParseAny());
EXPECT_EQ(t->GetLexicalUnitType(), Value::NUMBER);
EXPECT_EQ(t->GetDimension(), goldenunit);
EXPECT_FLOAT_EQ(t->GetFloatValue(), goldennum);
EXPECT_EQ(parselen, a.getpos() - s);
}
// Checks that ParseAny(s) returns goldennum with OTHER unit (with
// unit text goldenunit).
void TestAnyNumOtherUnit(const char* s, int parselen, float goldennum,
string goldenunit) {
SCOPED_TRACE(s);
Parser a(s);
if (parselen == -1) parselen = strlen(s);
scoped_ptr<Value> t(a.ParseAny());
EXPECT_EQ(t->GetLexicalUnitType(), Value::NUMBER);
EXPECT_EQ(t->GetDimension(), Value::OTHER);
EXPECT_EQ(t->GetDimensionUnitText(), goldenunit);
EXPECT_EQ(parselen, a.getpos() - s);
}
// Checks that ParseAny(s) returns a string-type value with type goldenty
// and value utf8golden.
void TestAnyString(const char* s, int parselen, Value::ValueType goldenty,
string utf8golden) {
SCOPED_TRACE(s);
Parser a(s);
if (parselen == -1) parselen = strlen(s);
scoped_ptr<Value> t(a.ParseAny());
EXPECT_EQ(goldenty, t->GetLexicalUnitType());
EXPECT_EQ(utf8golden, UnicodeTextToUTF8(t->GetStringValue()));
EXPECT_EQ(parselen, a.getpos() - s);
}
// Checks that ParseAny(s) returns a ident value with identifier goldenty
// and value utf8golden.
void TestAnyIdent(const char* s, int parselen, Identifier::Ident goldenty) {
SCOPED_TRACE(s);
Parser a(s);
if (parselen == -1) parselen = strlen(s);
scoped_ptr<Value> t(a.ParseAny());
EXPECT_EQ(Value::IDENT, t->GetLexicalUnitType());
EXPECT_EQ(goldenty, t->GetIdentifier().ident());
EXPECT_EQ(parselen, a.getpos() - s);
}
// Checks that ParseAny(s) returns OTHER identifier (with text goldenident).
void TestAnyOtherIdent(const char* s, int parselen,
const string& goldenident) {
SCOPED_TRACE(s);
Parser a(s);
if (parselen == -1) parselen = strlen(s);
scoped_ptr<Value> t(a.ParseAny());
EXPECT_EQ(Value::IDENT, t->GetLexicalUnitType());
EXPECT_EQ(Identifier::OTHER, t->GetIdentifier().ident());
EXPECT_EQ(goldenident, UnicodeTextToUTF8(t->GetIdentifierText()));
EXPECT_EQ(parselen, a.getpos() - s);
}
Declarations* ParseAndExpandBackground(const string& str,
bool quirks_mode=true) {
scoped_ptr<Parser> p(new Parser(str));
p->set_quirks_mode(quirks_mode);
scoped_ptr<Values> vals(p->ParseValues(Property::BACKGROUND));
if (vals.get() == NULL || vals->size() == 0) {
return NULL;
}
Declaration background(Property::BACKGROUND, vals.release(), false);
scoped_ptr<Declarations> decls(new Declarations);
Parser::ExpandBackground(background, decls.get());
if (decls->size() == 0) {
return NULL;
}
return decls.release();
}
void TestBackgroundPosition(const string& str, const string& x,
const string& y) {
scoped_ptr<Declarations> decls(ParseAndExpandBackground(str));
// Find and check position x and y values.
bool found_x = false;
bool found_y = false;
for (Declarations::const_iterator iter = decls->begin();
iter != decls->end(); ++iter) {
Declaration* decl = *iter;
switch (decl->prop()) {
case Property::BACKGROUND_POSITION_X:
EXPECT_EQ(x, decl->values()->get(0)->ToString());
found_x = true;
break;
case Property::BACKGROUND_POSITION_Y:
EXPECT_EQ(y, decl->values()->get(0)->ToString());
found_y = true;
break;
default:
break;
}
}
EXPECT_TRUE(found_x);
EXPECT_TRUE(found_y);
}
};
TEST_F(ParserTest, unescape) {
TestUnescape("\\abcdef aabc", 8, 0xABCDEF);
TestUnescape("\\A", 2, 0xA);
TestUnescape("\\A0b5C\r\n", 8, 0xa0b5C);
TestUnescape("\\AB ", 4, 0xAB);
}
TEST_F(ParserTest, ident) {
// We're a little more forgiving than the standard:
//
// In CSS 2.1, identifiers (including element names, classes, and
// IDs in selectors) can contain only the characters [A-Za-z0-9]
// and ISO 10646 characters U+00A1 and higher, plus the hyphen (-)
// and the underscore (_); they cannot start with a digit, or a
// hyphen followed by a digit. Only properties, values, units,
// pseudo-classes, pseudo-elements, and at-rules may start with a
// hyphen (-); other identifiers (e.g. element names, classes, or
// IDs) may not. Identifiers can also contain escaped characters
// and any ISO 10646 character as a numeric code (see next
// item). For instance, the identifier "B&W?" may be written as
// "B\&W\?" or "B\26 W\3F".
TestIdent("abcd rexo\n", 4, "abcd");
TestIdent("台灣華語", 12, "台灣華語");
TestIdent("\\41\\42 \\43 \\44", 14, "ABCD");
TestIdent("\\41\\42 \\43 \\44g'r,'rcg.,',", 15, "ABCDg");
TestIdent("\\41\\42 \\43 \\44\r\ng'r,'rcg.,',", 17, "ABCDg");
TestIdent("-blah-_67", 9, "-blah-_67");
TestIdent("\\!\\&\\^\\*\\\\e", 11, "!&^*\\e");
}
TEST_F(ParserTest, string) {
TestSstring("'ab\\'aoe\"\\'eo灣'灣", 17, "ab'aoe\"'eo灣");
TestDstring("\"ab'aoe\\\"'eo灣\"灣", 16, "ab'aoe\"'eo灣");
TestSstring("'ab\naoeu", 3, "ab");
TestDstring("\"ab\naoeu", 3, "ab");
TestDstring("\"ab\\\naoeu\"", 10, "abaoeu");
}
TEST_F(ParserTest, anynum) {
TestAnyNum("3.1415 4aone", 6, 3.1415, Value::NO_UNIT);
TestAnyNum(".1415 4aone", 5, .1415, Value::NO_UNIT);
TestAnyNum("5 4aone", 1, 5, Value::NO_UNIT);
TestAnyNum("3.1415pt 4aone", 8, 3.1415, Value::PT);
TestAnyNum(".1415pc 4aone", 7, .1415, Value::PC);
TestAnyNum("5s 4aone", 2, 5, Value::S);
TestAnyNumOtherUnit("5sacks 4aone", 6, 5, "sacks");
TestAnyNumOtherUnit("5灣 4aone", 4, 5, "灣");
}
TEST_F(ParserTest, anystring) {
TestAnyIdent("none b c d e", 4, Identifier::NONE);
TestAnyIdent("none; b c d e", 4, Identifier::NONE);
TestAnyIdent("none ; b c d e", 4, Identifier::NONE);
TestAnyOtherIdent("a b c d e", 1, "a");
TestAnyOtherIdent("a; b c d e", 1, "a");
TestAnyOtherIdent("a ; b c d e", 1, "a");
TestAnyString("'ab\\'aoe\"\\'eo灣'灣 ; b c d e", 17,
Value::STRING, "ab'aoe\"'eo灣");
}
TEST_F(ParserTest, color) {
// allowed in quirks mode
scoped_ptr<Parser> a(new Parser("abCdEF brc.,aoek"));
EXPECT_EQ(a->ParseColor().ToString(), "#abcdef");
// not allowed in stanard compliant mode.
a.reset(new Parser("abCdEF brc.,aoek"));
a->set_quirks_mode(false);
EXPECT_FALSE(a->ParseColor().IsDefined());
// this is allowed
a.reset(new Parser("#abCdEF brc.,aoek"));
a->set_quirks_mode(false);
EXPECT_EQ(a->ParseColor().ToString(), "#abcdef");
a.reset(new Parser("abC btneo"));
EXPECT_EQ(a->ParseColor().ToString(), "#aabbcc");
// no longer allowed
a.reset(new Parser("#white something"));
EXPECT_FALSE(a->ParseColor().IsDefined());
a.reset(new Parser("#white something"));
a->set_quirks_mode(false);
EXPECT_FALSE(a->ParseColor().IsDefined());
// this is allowed
a.reset(new Parser("white something"));
EXPECT_EQ(a->ParseColor().ToString(), "#ffffff");
a.reset(new Parser("white something"));
a->set_quirks_mode(false);
EXPECT_EQ(a->ParseColor().ToString(), "#ffffff");
// system color
a.reset(new Parser("buttonface something"));
EXPECT_EQ(a->ParseColor().ToString(), "#ece9d8");
// string patterns
a.reset(new Parser("'abCdEF' brc.,aoek"));
EXPECT_EQ(a->ParseColor().ToString(), "#abcdef");
a.reset(new Parser("'abCdEF' brc.,aoek"));
a->set_quirks_mode(false);
EXPECT_FALSE(a->ParseColor().IsDefined());
// this is not allowed since color values must end on string boundary
a.reset(new Parser("'#abCdEF brc'.,aoek"));
a->set_quirks_mode(false);
EXPECT_FALSE(a->ParseColor().IsDefined());
a.reset(new Parser("\"abC\" btneo"));
EXPECT_EQ(a->ParseColor().ToString(), "#aabbcc");
// no longer allowed
a.reset(new Parser("'#white' something"));
EXPECT_FALSE(a->ParseColor().IsDefined());
a.reset(new Parser("'#white' something"));
a->set_quirks_mode(false);
EXPECT_FALSE(a->ParseColor().IsDefined());
// this is allowed
a.reset(new Parser("'white' something"));
EXPECT_EQ(a->ParseColor().ToString(), "#ffffff");
a.reset(new Parser("'white' something"));
a->set_quirks_mode(false);
EXPECT_EQ(a->ParseColor().ToString(), "#ffffff");
// no longer allowed
a.reset(new Parser("100%"));
EXPECT_FALSE(a->ParseColor().IsDefined());
// no longer allowed
a.reset(new Parser("100px"));
EXPECT_FALSE(a->ParseColor().IsDefined());
// this is allowed
a.reset(new Parser("100"));
EXPECT_EQ(a->ParseColor().ToString(), "#110000");
// should be parsed as a number
a.reset(new Parser("100px"));
scoped_ptr<Value> t(a->ParseAnyExpectingColor());
EXPECT_EQ(Value::NUMBER, t->GetLexicalUnitType());
EXPECT_EQ("100px", t->ToString());
a.reset(new Parser("rgb(12,25,30)"));
t.reset(a->ParseAny());
EXPECT_EQ(t->GetColorValue().ToString(), "#0c191e");
a.reset(new Parser("rgb( 12% , 25%, 30%)"));
t.reset(a->ParseAny());
EXPECT_EQ(t->GetColorValue().ToString(), "#1e3f4c");
a.reset(new Parser("rgb( 12% , 25% 30%)"));
t.reset(a->ParseAny());
EXPECT_FALSE(t.get());
}
TEST_F(ParserTest, url) {
scoped_ptr<Parser> a(new Parser("url(blah)"));
scoped_ptr<Value> t(a->ParseAny());
EXPECT_EQ(Value::URI, t->GetLexicalUnitType());
EXPECT_EQ("blah", UnicodeTextToUTF8(t->GetStringValue()));
a.reset(new Parser("url( blah )"));
t.reset(a->ParseAny());
EXPECT_EQ(Value::URI, t->GetLexicalUnitType());
EXPECT_EQ("blah", UnicodeTextToUTF8(t->GetStringValue()));
a.reset(new Parser("url( blah extra)"));
t.reset(a->ParseAny());
EXPECT_EQ(static_cast<Value *>(NULL), t.get());
}
TEST_F(ParserTest, rect) {
// rect can be either comma or space delimited
scoped_ptr<Parser> a(new Parser("rect( 12, 10,auto 200px)"));
scoped_ptr<Value> t(a->ParseAny());
EXPECT_EQ(Value::RECT, t->GetLexicalUnitType());
ASSERT_EQ(4, t->GetParameters()->size());
EXPECT_EQ(Value::NUMBER,
t->GetParameters()->get(0)->GetLexicalUnitType());
EXPECT_EQ(12, t->GetParameters()->get(0)->GetIntegerValue());
EXPECT_EQ(Value::IDENT,
t->GetParameters()->get(2)->GetLexicalUnitType());
EXPECT_EQ(Identifier::AUTO,
t->GetParameters()->get(2)->GetIdentifier().ident());
a.reset(new Parser("rect(auto)"));
t.reset(a->ParseAny());
EXPECT_EQ(static_cast<Value *>(NULL), t.get());
a.reset(new Parser("rect()"));
t.reset(a->ParseAny());
EXPECT_EQ(static_cast<Value *>(NULL), t.get());
a.reset(new Parser("rect(13 10 auto 4)"));
t.reset(a->ParseAny());
EXPECT_EQ(13, t->GetParameters()->get(0)->GetIntegerValue());
a.reset(new Parser("rect(14,10,1,2)"));
t.reset(a->ParseAny());
EXPECT_EQ(14, t->GetParameters()->get(0)->GetIntegerValue());
a.reset(new Parser("rect(15 10 1)"));
t.reset(a->ParseAny());
EXPECT_EQ(static_cast<Value *>(NULL), t.get());
a.reset(new Parser("rect(16 10 1 2 3)"));
t.reset(a->ParseAny());
EXPECT_EQ(static_cast<Value *>(NULL), t.get());
}
TEST_F(ParserTest, background) {
scoped_ptr<Declarations> decls(ParseAndExpandBackground("#333"));
EXPECT_EQ(6, decls->size());
decls.reset(ParseAndExpandBackground("fff"));
EXPECT_TRUE(decls.get());
// Not valid for quirks_mode=false
EXPECT_FALSE(ParseAndExpandBackground("fff", false));
decls.reset(ParseAndExpandBackground("fff000"));
EXPECT_TRUE(decls.get());
// Not valid for quirks_mode=false
EXPECT_FALSE(ParseAndExpandBackground("fff000", false));
// This should now be parsed as background position instead of color.
decls.reset(ParseAndExpandBackground("100%"));
ASSERT_TRUE(decls.get());
ASSERT_EQ(6, decls->size());
EXPECT_EQ(Property::BACKGROUND_COLOR, decls->get(0)->prop());
EXPECT_EQ(Identifier::TRANSPARENT,
decls->get(0)->values()->get(0)->GetIdentifier().ident());
EXPECT_EQ(Property::BACKGROUND_POSITION_X, decls->get(4)->prop());
EXPECT_EQ("100%", decls->get(4)->values()->get(0)->ToString());
EXPECT_FALSE(ParseAndExpandBackground(""));
EXPECT_FALSE(ParseAndExpandBackground(";"));
EXPECT_FALSE(ParseAndExpandBackground("\"string\""));
EXPECT_FALSE(ParseAndExpandBackground("normal"));
decls.reset(ParseAndExpandBackground("inherit"));
ASSERT_EQ(6, decls->size());
for (int i = 0; i < 6; ++i) {
EXPECT_EQ(Identifier::INHERIT,
decls->get(i)->values()->get(0)->GetIdentifier().ident());
}
EXPECT_FALSE(ParseAndExpandBackground("inherit none"));
EXPECT_FALSE(ParseAndExpandBackground("none inherit"));
decls.reset(ParseAndExpandBackground("none"));
EXPECT_EQ(Identifier::TRANSPARENT,
decls->get(0)->values()->get(0)->GetIdentifier().ident());
EXPECT_EQ(Identifier::NONE,
decls->get(1)->values()->get(0)->GetIdentifier().ident());
EXPECT_EQ(Identifier::REPEAT,
decls->get(2)->values()->get(0)->GetIdentifier().ident());
EXPECT_EQ(Identifier::SCROLL,
decls->get(3)->values()->get(0)->GetIdentifier().ident());
decls.reset(ParseAndExpandBackground("fixed"));
EXPECT_EQ(Identifier::FIXED,
decls->get(3)->values()->get(0)->GetIdentifier().ident());
decls.reset(ParseAndExpandBackground("transparent"));
EXPECT_EQ(Identifier::TRANSPARENT,
decls->get(0)->values()->get(0)->GetIdentifier().ident());
// IE specific. Firefox should bail out.
decls.reset(ParseAndExpandBackground("none url(abc)"));
EXPECT_EQ(Value::URI, decls->get(1)->values()->get(0)->GetLexicalUnitType());
decls.reset(ParseAndExpandBackground("none red fixed"));
EXPECT_EQ("#ff0000",
decls->get(0)->values()->get(0)->GetColorValue().ToString());
EXPECT_EQ(Identifier::NONE,
decls->get(1)->values()->get(0)->GetIdentifier().ident());
EXPECT_EQ(Identifier::FIXED,
decls->get(3)->values()->get(0)->GetIdentifier().ident());
// The rest are position tests
TestBackgroundPosition("none", "0%", "0%");
TestBackgroundPosition("10", "10", "50%");
TestBackgroundPosition("10 20%", "10", "20%");
TestBackgroundPosition("10 100%", "10", "100%");
TestBackgroundPosition("top left", "left", "top");
TestBackgroundPosition("left top", "left", "top");
TestBackgroundPosition("bottom", "50%", "bottom");
TestBackgroundPosition("bottom center", "center", "bottom");
TestBackgroundPosition("center bottom", "center", "bottom");
TestBackgroundPosition("left", "left", "50%");
TestBackgroundPosition("left center", "left", "center");
TestBackgroundPosition("center left", "left", "center");
TestBackgroundPosition("center", "center", "50%");
TestBackgroundPosition("center center", "center", "center");
TestBackgroundPosition("center 30%", "center", "30%");
TestBackgroundPosition("30% center", "30%", "center");
TestBackgroundPosition("30% bottom", "30%", "bottom");
TestBackgroundPosition("left 30%", "left", "30%");
TestBackgroundPosition("30% left", "left", "30%");
// IE specific
TestBackgroundPosition("30% 20% 50%", "30%", "20%");
TestBackgroundPosition("bottom center right", "center", "bottom");
TestBackgroundPosition("bottom right top", "right", "bottom");
TestBackgroundPosition("bottom top right", "right", "top");
TestBackgroundPosition("top right left", "right", "top");
TestBackgroundPosition("right left top", "left", "top");
}
TEST_F(ParserTest, font_family) {
scoped_ptr<Parser> a(new Parser(
" Arial font 'Sans' system, menu new "));
scoped_ptr<Values> t(new Values);
EXPECT_TRUE(a->ParseFontFamily(t.get()));
ASSERT_EQ(4, t->size());
EXPECT_EQ(Value::IDENT, t->get(0)->GetLexicalUnitType());
EXPECT_EQ("arial font", UnicodeTextToUTF8(t->get(0)->GetIdentifierText()));
EXPECT_EQ(Value::STRING, t->get(1)->GetLexicalUnitType());
EXPECT_EQ("system", UnicodeTextToUTF8(t->get(2)->GetIdentifierText()));
EXPECT_EQ("menu new", UnicodeTextToUTF8(t->get(3)->GetIdentifierText()));
a.reset(new Parser("Verdana 3"));
t.reset(new Values);
EXPECT_FALSE(a->ParseFontFamily(t.get()));
a.reset(new Parser("Verdana :"));
t.reset(new Values);
EXPECT_FALSE(a->ParseFontFamily(t.get()));
a.reset(new Parser("Verdana ;"));
t.reset(new Values);
EXPECT_TRUE(a->ParseFontFamily(t.get()));
ASSERT_EQ(1, t->size());
EXPECT_EQ(Value::IDENT, t->get(0)->GetLexicalUnitType());
EXPECT_EQ("verdana", UnicodeTextToUTF8(t->get(0)->GetIdentifierText()));
}
TEST_F(ParserTest, font) {
scoped_ptr<Parser> a(new Parser("caption"));
scoped_ptr<Values> t(a->ParseFont());
ASSERT_EQ(6, t->size());
EXPECT_EQ(Identifier::NORMAL, t->get(0)->GetIdentifier().ident());
EXPECT_EQ(Identifier::NORMAL, t->get(1)->GetIdentifier().ident());
EXPECT_EQ(Identifier::NORMAL, t->get(2)->GetIdentifier().ident());
EXPECT_FLOAT_EQ(32.0/3, t->get(3)->GetFloatValue());
EXPECT_EQ(Value::PX, t->get(3)->GetDimension());
EXPECT_EQ(Identifier::NORMAL, t->get(4)->GetIdentifier().ident());
EXPECT_EQ(Identifier::CAPTION, t->get(5)->GetIdentifier().ident());
a.reset(new Parser("inherit"));
t.reset(a->ParseFont());
ASSERT_EQ(6, t->size());
for (int i = 0; i < 6; ++i)
EXPECT_EQ(Identifier::INHERIT, t->get(i)->GetIdentifier().ident());
a.reset(new Parser("normal 10px /120% Arial 'Sans'"));
t.reset(a->ParseFont());
ASSERT_EQ(7, t->size());
EXPECT_FLOAT_EQ(10, t->get(3)->GetFloatValue());
EXPECT_EQ(Value::PERCENT, t->get(4)->GetDimension());
a.reset(new Parser("italic 10px Arial, Sans"));
t.reset(a->ParseFont());
ASSERT_EQ(7, t->size());
EXPECT_FLOAT_EQ(10, t->get(3)->GetFloatValue());
EXPECT_EQ(Identifier::NORMAL, t->get(4)->GetIdentifier().ident());
a.reset(new Parser("SMALL-caps normal x-large Arial"));
t.reset(a->ParseFont());
ASSERT_EQ(6, t->size());
EXPECT_EQ(Identifier::NORMAL, t->get(0)->GetIdentifier().ident());
EXPECT_EQ(Identifier::SMALL_CAPS, t->get(1)->GetIdentifier().ident());
EXPECT_EQ(Identifier::X_LARGE, t->get(3)->GetIdentifier().ident());
EXPECT_EQ(Identifier::NORMAL, t->get(4)->GetIdentifier().ident());
a.reset(new Parser("bolder 100 120 Arial"));
t.reset(a->ParseFont());
ASSERT_EQ(6, t->size());
EXPECT_EQ(100, t->get(2)->GetIntegerValue());
EXPECT_EQ(120, t->get(3)->GetIntegerValue());
EXPECT_EQ(Identifier::NORMAL, t->get(4)->GetIdentifier().ident());
a.reset(new Parser("10px normal"));
t.reset(a->ParseFont());
ASSERT_EQ(6, t->size());
EXPECT_EQ(10, t->get(3)->GetIntegerValue());
EXPECT_EQ(Identifier::NORMAL, t->get(5)->GetIdentifier().ident());
a.reset(new Parser("normal 10px "));
t.reset(a->ParseFont());
EXPECT_EQ(5, t->size()) << "missing font-family should be allowed";
a.reset(new Parser("10px/12pt "));
t.reset(a->ParseFont());
EXPECT_EQ(5, t->size()) << "missing font-family should be allowed";
a.reset(new Parser("menu 10px"));
t.reset(a->ParseFont());
EXPECT_EQ(static_cast<Values *>(NULL), t.get())
<< "system font with extra value";
a.reset(new Parser("Arial, menu "));
t.reset(a->ParseFont());
EXPECT_EQ(static_cast<Values *>(NULL), t.get()) << "missing font-size";
a.reset(new Parser("transparent 10px "));
t.reset(a->ParseFont());
EXPECT_EQ(static_cast<Values *>(NULL), t.get()) << "unknown property";
a.reset(new Parser("normal / 10px Arial"));
t.reset(a->ParseFont());
EXPECT_EQ(static_cast<Values *>(NULL), t.get())
<< "line-height without font-size";
a.reset(new Parser("normal 10px/ Arial"));
t.reset(a->ParseFont());
EXPECT_EQ(static_cast<Values *>(NULL), t.get())
<< "slash without line-height";
a.reset(new Parser("normal 10px Arial #333"));
t.reset(a->ParseFont());
EXPECT_EQ(static_cast<Values *>(NULL), t.get()) << "invalid type";
}
TEST_F(ParserTest, values) {
scoped_ptr<Parser> a(new Parser(
"rgb(12,25,30) url(blah) url('blah.png') 12% !important 'arial'"));
scoped_ptr<Values> t(a->ParseValues(Property::OTHER));
ASSERT_EQ(4, t->size());
EXPECT_EQ(Value::COLOR, t->get(0)->GetLexicalUnitType());
EXPECT_EQ(Value::URI, t->get(1)->GetLexicalUnitType());
EXPECT_EQ(Value::URI, t->get(2)->GetLexicalUnitType());
EXPECT_EQ(Value::NUMBER, t->get(3)->GetLexicalUnitType());
EXPECT_EQ(Value::PERCENT, t->get(3)->GetDimension());
a.reset(new Parser("rgb( 12, 25,30) @ignored url( blah )"
" rect(12 10 auto 200px)"
" { should be {nested }discarded } ident;"));
t.reset(a->ParseValues(Property::OTHER));
ASSERT_EQ(4, t->size());
EXPECT_EQ(Value::COLOR, t->get(0)->GetLexicalUnitType());
EXPECT_EQ(Value::URI, t->get(1)->GetLexicalUnitType());
EXPECT_EQ(Value::RECT, t->get(2)->GetLexicalUnitType());
EXPECT_EQ(Value::IDENT, t->get(3)->GetLexicalUnitType());
EXPECT_EQ("ident", UnicodeTextToUTF8(t->get(3)->GetIdentifierText()));
// test value copy constructor.
scoped_ptr<Value> val(new Value(*(t->get(2))));
EXPECT_EQ(Value::RECT, val->GetLexicalUnitType());
ASSERT_EQ(4, val->GetParameters()->size());
EXPECT_EQ(Value::NUMBER,
val->GetParameters()->get(0)->GetLexicalUnitType());
EXPECT_EQ(12, val->GetParameters()->get(0)->GetIntegerValue());
EXPECT_EQ(Value::IDENT,
val->GetParameters()->get(2)->GetLexicalUnitType());
EXPECT_EQ("auto", UnicodeTextToUTF8(val->GetParameters()->get(2)
->GetIdentifierText()));
}
void ParseFontFamily(Parser* parser) {
Values values;
parser->ParseFontFamily(&values);
}
TEST_F(ParserTest, ParseBlock) {
static const char* truetestcases[] = {
"{{{{}}}} serif",
"{ { { { } } } } serif", // whitespace
"{@ident1{{ @ident {}}}} serif", // @-idents
"{{}} @ident {{}} @ident serif", // multiple blocks
"{{ident{{}ident2}}} serif", // idents
};
for (int i = 0; i < arraysize(truetestcases); ++i) {
SCOPED_TRACE(truetestcases[i]);
Values values;
EXPECT_TRUE(Parser(truetestcases[i]).ParseFontFamily(&values));
ASSERT_EQ(1, values.size());
EXPECT_EQ(Value::IDENT, values.get(0)->GetLexicalUnitType());
EXPECT_EQ("serif", UnicodeTextToUTF8(values.get(0)->GetIdentifierText()));
}
static const char* falsetestcases[] = {
"{{{{}}} serif", // too many opens
"{{{{}}}}} serif", // too many closes
"{{{{}}}}}", // no tokenxs
};
for (int i = 0; i < arraysize(falsetestcases); ++i) {
SCOPED_TRACE(falsetestcases[i]);
Values values;
Parser(falsetestcases[i]).ParseFontFamily(&values);
EXPECT_EQ(0, values.size());
}
}
TEST_F(ParserTest, declarations) {
scoped_ptr<Parser> a(new Parser(
"color: #333; line-height: 1.3;"
"text-align: justify; font-family: \"Gill Sans MT\","
"\"Gill Sans\", GillSans, Arial, Helvetica, sans-serif"));
scoped_ptr<Declarations> t(a->ParseDeclarations());
// Declarations is a vector of Declaration, and we go through them:
ASSERT_EQ(4, t->size());
EXPECT_EQ(Property::COLOR, t->get(0)->prop());
EXPECT_EQ(Property::LINE_HEIGHT, t->get(1)->prop());
EXPECT_EQ(Property::TEXT_ALIGN, t->get(2)->prop());
EXPECT_EQ(Property::FONT_FAMILY, t->get(3)->prop());
ASSERT_EQ(1, t->get(0)->values()->size());
EXPECT_EQ(Value::COLOR, t->get(0)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ("#333333", t->get(0)->values()->get(0)->GetColorValue().ToString());
ASSERT_EQ(6, (*t->get(3)->values()).size());
EXPECT_EQ(Value::STRING,
t->get(3)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ("Gill Sans MT",
UnicodeTextToUTF8(t->get(3)->values()->get(0)->GetStringValue()));
a.reset(new Parser(
"background-color: 333; color: \"abcdef\";"
"background-color: #red; color: \"white\";"
"background-color: rgb(255, 10%, 10)"));
t.reset(a->ParseDeclarations());
ASSERT_EQ(4, t->size()) << "#red is not valid";
EXPECT_EQ(Property::BACKGROUND_COLOR, t->get(0)->prop());
EXPECT_EQ(Property::COLOR, t->get(1)->prop());
EXPECT_EQ(Property::COLOR, t->get(2)->prop());
EXPECT_EQ(Property::BACKGROUND_COLOR, t->get(3)->prop());
ASSERT_EQ(1, t->get(0)->values()->size());
EXPECT_EQ(Value::COLOR, t->get(0)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ("#333333", t->get(0)->values()->get(0)->GetColorValue().ToString());
ASSERT_EQ(1, t->get(1)->values()->size());
EXPECT_EQ(Value::COLOR, t->get(1)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ("#abcdef", t->get(1)->values()->get(0)->GetColorValue().ToString());
ASSERT_EQ(1, t->get(2)->values()->size());
EXPECT_EQ(Value::COLOR, t->get(2)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ("#ffffff", t->get(2)->values()->get(0)->GetColorValue().ToString());
ASSERT_EQ(1, t->get(3)->values()->size());
EXPECT_EQ(Value::COLOR, t->get(3)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ("#ff190a", t->get(3)->values()->get(0)->GetColorValue().ToString());
// expand background
a.reset(new Parser("background: #333 fixed no-repeat; "));
t.reset(a->ParseDeclarations());
ASSERT_EQ(7, t->size());
EXPECT_EQ(Property::BACKGROUND, t->get(0)->prop());
EXPECT_EQ(3, t->get(0)->values()->size());
EXPECT_EQ(Property::BACKGROUND_COLOR, t->get(1)->prop());
EXPECT_EQ(1, t->get(1)->values()->size());
EXPECT_EQ(Property::BACKGROUND_IMAGE, t->get(2)->prop());
EXPECT_EQ(1, t->get(2)->values()->size());
EXPECT_EQ(Property::BACKGROUND_REPEAT, t->get(3)->prop());
EXPECT_EQ(1, t->get(3)->values()->size());
EXPECT_EQ(Property::BACKGROUND_ATTACHMENT, t->get(4)->prop());
EXPECT_EQ(1, t->get(4)->values()->size());
EXPECT_EQ(Property::BACKGROUND_POSITION_X, t->get(5)->prop());
EXPECT_EQ(1, t->get(5)->values()->size());
EXPECT_EQ(Property::BACKGROUND_POSITION_Y, t->get(6)->prop());
EXPECT_EQ(1, t->get(6)->values()->size());
// expand font
a.reset(new Parser("font: small-caps 24px Arial 'Sans', monospace; "));
t.reset(a->ParseDeclarations());
ASSERT_EQ(7, t->size());
EXPECT_EQ(Property::FONT, t->get(0)->prop());
EXPECT_EQ(8, t->get(0)->values()->size());
EXPECT_EQ(Property::FONT_STYLE, t->get(1)->prop());
EXPECT_EQ(Property::FONT_VARIANT, t->get(2)->prop());
EXPECT_EQ(Property::FONT_WEIGHT, t->get(3)->prop());
EXPECT_EQ(Property::FONT_SIZE, t->get(4)->prop());
EXPECT_EQ(Property::LINE_HEIGHT, t->get(5)->prop());
EXPECT_EQ(Property::FONT_FAMILY, t->get(6)->prop());
ASSERT_EQ(3, t->get(6)->values()->size());
EXPECT_EQ("monospace", UnicodeTextToUTF8(t->get(6)->values()->get(2)->
GetIdentifierText()));
a.reset(new Parser(
"{font-size: #333; color:red"));
t.reset(a->ParseDeclarations());
ASSERT_EQ(1, t->size());
EXPECT_EQ(Property::COLOR, t->get(0)->prop());
a.reset(new Parser(
"{font-size: #333; color:red"));
a->set_quirks_mode(false);
t.reset(a->ParseDeclarations());
EXPECT_EQ(0, t->size());
a.reset(new Parser(
"font-size {background: #333; color:red"));
t.reset(a->ParseDeclarations());
ASSERT_EQ(1, t->size());
EXPECT_EQ(Property::COLOR, t->get(0)->prop());
a.reset(new Parser(
"font-size {background: #333; color:red"));
a->set_quirks_mode(false);
t.reset(a->ParseDeclarations());
EXPECT_EQ(0, t->size());
a.reset(new Parser(
"font-size }background: #333; color:red"));
t.reset(a->ParseDeclarations());
EXPECT_EQ(0, t->size());
a.reset(new Parser(
"font-size }background: #333; color:red"));
a->set_quirks_mode(false);
t.reset(a->ParseDeclarations());
EXPECT_EQ(0, t->size());
a.reset(new Parser(
"top:1px; {font-size: #333; color:red}"));
t.reset(a->ParseDeclarations());
ASSERT_EQ(2, t->size());
EXPECT_EQ(Property::TOP, t->get(0)->prop());
EXPECT_EQ(Property::COLOR, t->get(1)->prop());
a.reset(new Parser(
"top:1px; {font-size: #333; color:red}"));
a->set_quirks_mode(false);
t.reset(a->ParseDeclarations());
ASSERT_EQ(1, t->size());
EXPECT_EQ(Property::TOP, t->get(0)->prop());
}
TEST_F(ParserTest, illegal_constructs) {
scoped_ptr<Parser> a(new Parser("width: {$width}"));
scoped_ptr<Declarations> t(a->ParseDeclarations());
ASSERT_EQ(1, t->size());
EXPECT_EQ(Property::WIDTH, t->get(0)->prop());
EXPECT_EQ(0, t->get(0)->values()->size());
a.reset(new Parser("font-family: \"Gill Sans MT;"));
t.reset(a->ParseDeclarations());
ASSERT_EQ(1, t->size());
EXPECT_EQ(Property::FONT_FAMILY, t->get(0)->prop());
ASSERT_EQ(1, t->get(0)->values()->size());
EXPECT_EQ(Value::STRING, t->get(0)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ("Gill Sans MT;",
UnicodeTextToUTF8(t->get(0)->values()->get(0)->GetStringValue()));
a.reset(new Parser("font-family: 'Gill Sans MT"));
t.reset(a->ParseDeclarations());
ASSERT_EQ(1, t->size());
EXPECT_EQ(Property::FONT_FAMILY, t->get(0)->prop());
ASSERT_EQ(1, t->get(0)->values()->size());
EXPECT_EQ(Value::STRING, t->get(0)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ("Gill Sans MT",
UnicodeTextToUTF8(t->get(0)->values()->get(0)->GetStringValue()));
}
TEST_F(ParserTest, value_validation) {
scoped_ptr<Parser> a(new Parser("width: {$width}"));
scoped_ptr<Declarations> t(a->ParseDeclarations());
// Let's take border-color as an example. It only accepts color and the
// transparent keyword in particular (and inherit is a common one).
a.reset(new Parser(
"border-color: \"string\"; "
"border-color: url(\"abc\"); "
"border-color: 12345; "
"border-color: none; "
"border-color: inherited; "
"border-color: red; "
"border-color: #123456; "
"border-color: transparent; "
"border-color: inherit; "
"border-color: unknown; "
));
t.reset(a->ParseDeclarations());
ASSERT_EQ(4, t->size());
EXPECT_EQ(Value::COLOR, t->get(0)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ(Value::COLOR, t->get(1)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ(Value::IDENT, t->get(2)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ(Identifier::TRANSPARENT,
t->get(2)->values()->get(0)->GetIdentifier().ident());
EXPECT_EQ(Value::IDENT, t->get(3)->values()->get(0)->GetLexicalUnitType());
EXPECT_EQ(Identifier::INHERIT,
t->get(3)->values()->get(0)->GetIdentifier().ident());
}
TEST_F(ParserTest, universalselector) {
Parser p("*");
scoped_ptr<SimpleSelectors> t(p.ParseSimpleSelectors(false));
EXPECT_EQ(SimpleSelectors::NONE, t->combinator());
ASSERT_EQ(1, t->size());
EXPECT_EQ(SimpleSelector::UNIVERSAL, t->get(0)->type());
}
TEST_F(ParserTest, universalselectorcondition) {
scoped_ptr<Parser> a(new Parser(" *[foo=bar]"));
scoped_ptr<SimpleSelectors> t(a->ParseSimpleSelectors(true));
EXPECT_EQ(SimpleSelectors::DESCENDANT, t->combinator());
ASSERT_EQ(2, t->size());
EXPECT_EQ(SimpleSelector::UNIVERSAL, t->get(0)->type());
EXPECT_EQ(SimpleSelector::EXACT_ATTRIBUTE, t->get(1)->type());
a.reset(new Parser(" *[foo="));
t.reset(a->ParseSimpleSelectors(true));
// This is not a valid selector.
EXPECT_FALSE(t.get());
}
TEST_F(ParserTest, comment_breaking_descendant_combinator) {
Parser p(" a b/*foo*/c /*foo*/d/*foo*/ e { }");
scoped_ptr<Ruleset> t(p.ParseRuleset());
ASSERT_EQ(1, t->selectors().size());
const Selector* s = t->selectors().get(0);
ASSERT_EQ(5, s->size());
EXPECT_EQ(SimpleSelectors::NONE, s->get(0)->combinator());
EXPECT_EQ(SimpleSelectors::DESCENDANT, s->get(1)->combinator());
EXPECT_EQ(SimpleSelectors::DESCENDANT, s->get(2)->combinator());
EXPECT_EQ(SimpleSelectors::DESCENDANT, s->get(3)->combinator());
EXPECT_EQ(SimpleSelectors::DESCENDANT, s->get(4)->combinator());
}
TEST_F(ParserTest, comment_breaking_child_combinator) {
Parser p(" a >b/*f>oo*/>c /*fo>o*/>d/*f>oo*/ > e>f { }");
scoped_ptr<Ruleset> t(p.ParseRuleset());
ASSERT_EQ(1, t->selectors().size());
const Selector* s = t->selectors().get(0);
ASSERT_EQ(6, s->size());
EXPECT_EQ(SimpleSelectors::NONE, s->get(0)->combinator());
EXPECT_EQ(SimpleSelectors::CHILD, s->get(1)->combinator());
EXPECT_EQ(SimpleSelectors::CHILD, s->get(2)->combinator());
EXPECT_EQ(SimpleSelectors::CHILD, s->get(3)->combinator());
EXPECT_EQ(SimpleSelectors::CHILD, s->get(4)->combinator());
EXPECT_EQ(SimpleSelectors::CHILD, s->get(5)->combinator());
}
TEST_F(ParserTest, ruleset_starts_with_combinator) {
Parser p(" >a { }");
scoped_ptr<Ruleset> t(p.ParseRuleset());
// This is not a valid selector.
EXPECT_FALSE(t.get());
}
TEST_F(ParserTest, simple_selectors) {
// First, a basic case
scoped_ptr<Parser> a(new Parser("*[lang|=fr]"));
scoped_ptr<SimpleSelectors> t(a->ParseSimpleSelectors(false));
EXPECT_EQ(SimpleSelectors::NONE, t->combinator());
{
const SimpleSelector* c = t->get(0);
ASSERT_TRUE(c != NULL);
ASSERT_EQ(SimpleSelector::UNIVERSAL, c->type());
}
{
const SimpleSelector* c = t->get(1);
ASSERT_TRUE(c != NULL);
ASSERT_EQ(SimpleSelector::BEGIN_HYPHEN_ATTRIBUTE, c->type());
EXPECT_EQ("lang", UnicodeTextToUTF8(c->attribute()));
EXPECT_EQ("fr", UnicodeTextToUTF8(c->value()));
}
// Now, a very complex one.
a.reset(new Parser(
"> P:first_child:hover[class~='hidden'][width]#content"
"[id*=logo][id^=logo][id$=\"logo\"]"
"[lang=en].anotherclass.moreclass #next"));
t.reset(a->ParseSimpleSelectors(true));
EXPECT_EQ(SimpleSelectors::CHILD, t->combinator());
// We're going to go through the conditions in reverse order, for kicks.
SimpleSelectors::const_reverse_iterator it = t->rbegin();
// .moreclass
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::CLASS, c->type());
EXPECT_EQ("moreclass", UnicodeTextToUTF8(c->value()));
}
// .anotherclass
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::CLASS, c->type());
EXPECT_EQ("anotherclass", UnicodeTextToUTF8(c->value()));
}
// EXACT_ATTRIBUTE [lang=en]
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::EXACT_ATTRIBUTE, c->type());
EXPECT_EQ("lang", UnicodeTextToUTF8(c->attribute()));
EXPECT_EQ("en", UnicodeTextToUTF8(c->value()));
}
// END_WITH_ATTRIBUTE [id$="logo"]
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::END_WITH_ATTRIBUTE, c->type());
EXPECT_EQ("id", UnicodeTextToUTF8(c->attribute()));
EXPECT_EQ("logo", UnicodeTextToUTF8(c->value()));
}
// BEGIN_WITH_ATTRIBUTE [id^=logo]
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::BEGIN_WITH_ATTRIBUTE, c->type());
EXPECT_EQ("id", UnicodeTextToUTF8(c->attribute()));
EXPECT_EQ("logo", UnicodeTextToUTF8(c->value()));
}
// SUBSTRING_ATTRIBUTE [id*=logo]
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::SUBSTRING_ATTRIBUTE, c->type());
EXPECT_EQ("id", UnicodeTextToUTF8(c->attribute()));
EXPECT_EQ("logo", UnicodeTextToUTF8(c->value()));
}
// ID #content
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::ID, c->type());
EXPECT_EQ("content", UnicodeTextToUTF8(c->value()));
}
// EXIST_ATTRIBUTE [width]
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::EXIST_ATTRIBUTE, c->type());
EXPECT_EQ("width", UnicodeTextToUTF8(c->attribute()));
}
// ONE_OF_ATTRIBUTE [class~=hidden]
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::ONE_OF_ATTRIBUTE, c->type());
EXPECT_EQ("class", UnicodeTextToUTF8(c->attribute()));
EXPECT_EQ("hidden", UnicodeTextToUTF8(c->value()));
}
// PSEUDOCLASS :hover
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::PSEUDOCLASS, c->type());
EXPECT_EQ("hover", UnicodeTextToUTF8(c->pseudoclass()));
}
// PSEUDOCLASS :first_child
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::PSEUDOCLASS, c->type());
EXPECT_EQ("first_child", UnicodeTextToUTF8(c->pseudoclass()));
}
// P
{
SimpleSelector* c = *it++;
ASSERT_EQ(SimpleSelector::ELEMENT_TYPE, c->type());
EXPECT_EQ("P", UnicodeTextToUTF8(c->element_text()));
EXPECT_EQ(kHtmlTagP, c->element_type());
}
ASSERT_TRUE(it == t->rend());
}
TEST_F(ParserTest, bad_simple_selectors) {
scoped_ptr<Parser> a;
scoped_ptr<SimpleSelectors> t;
// valid or not?
a.reset(new Parser(""));
t.reset(a->ParseSimpleSelectors(false));
EXPECT_FALSE(t.get());
a.reset(new Parser("{}"));
t.reset(a->ParseSimpleSelectors(false));
EXPECT_FALSE(t.get());
a.reset(new Parser("#"));
t.reset(a->ParseSimpleSelectors(false));
EXPECT_FALSE(t.get());
a.reset(new Parser("# {"));
t.reset(a->ParseSimpleSelectors(false));
EXPECT_FALSE(t.get());
a.reset(new Parser("#{"));
t.reset(a->ParseSimpleSelectors(false));
EXPECT_FALSE(t.get());
a.reset(new Parser("##"));
t.reset(a->ParseSimpleSelectors(false));
EXPECT_FALSE(t.get());
a.reset(new Parser("*[class="));
t.reset(a->ParseSimpleSelectors(false));
EXPECT_FALSE(t.get());
a.reset(new Parser("*[class=hidden];"));
t.reset(a->ParseSimpleSelectors(false));
EXPECT_FALSE(t.get());
a.reset(new Parser("*[class=hidden].;"));
t.reset(a->ParseSimpleSelectors(false));
EXPECT_FALSE(t.get());
a.reset(new Parser("#a {"));
t.reset(a->ParseSimpleSelectors(false));
EXPECT_TRUE(t.get());
}
TEST_F(ParserTest, selectors) {
scoped_ptr<Parser> a(new Parser("h1 p #id {"));
scoped_ptr<Selectors> t(a->ParseSelectors());
EXPECT_TRUE(t.get());
ASSERT_EQ(1, t->size());
EXPECT_EQ(3, (*t)[0]->size());
EXPECT_EQ('{', *a->getpos());
a.reset(new Parser(" h1 p #id , div.p > h2 > div.t #id"));
t.reset(a->ParseSelectors());
EXPECT_TRUE(t.get());
ASSERT_EQ(2, t->size());
EXPECT_EQ(3, (*t)[0]->size());
EXPECT_EQ(4, (*t)[1]->size());
EXPECT_TRUE(a->Done());
a.reset(new Parser("/*c*/h1 p #id/*c*/,/*c*/div.p > h2 > div.t #id/*c*/"));
t.reset(a->ParseSelectors());
EXPECT_TRUE(t.get());
ASSERT_EQ(2, t->size());
EXPECT_EQ(3, (*t)[0]->size());
EXPECT_EQ(4, (*t)[1]->size());
EXPECT_TRUE(a->Done());
a.reset(new Parser("{}"));
t.reset(a->ParseSelectors());
EXPECT_FALSE(t.get());
EXPECT_EQ('{', *a->getpos());
a.reset(new Parser(""));
t.reset(a->ParseSelectors());
EXPECT_FALSE(t.get());
EXPECT_TRUE(a->Done());
a.reset(new Parser(" ,h1 p #id {"));
t.reset(a->ParseSelectors());
EXPECT_FALSE(t.get());
EXPECT_EQ('{', *a->getpos());
a.reset(new Parser(" , {"));
t.reset(a->ParseSelectors());
EXPECT_FALSE(t.get());
EXPECT_EQ('{', *a->getpos());
a.reset(new Parser("h1 p #id, {"));
t.reset(a->ParseSelectors());
EXPECT_FALSE(t.get());
EXPECT_EQ('{', *a->getpos());
a.reset(new Parser("h1 p #id, {"));
t.reset(a->ParseSelectors());
EXPECT_FALSE(t.get());
EXPECT_EQ('{', *a->getpos());
a.reset(new Parser("h1 p #id;"));
t.reset(a->ParseSelectors());
EXPECT_FALSE(t.get());
EXPECT_TRUE(a->Done());
a.reset(new Parser(" h1 p[class=/*{*/ #id , h2 #id"));
t.reset(a->ParseSelectors());
EXPECT_FALSE(t.get());
EXPECT_TRUE(a->Done());
a.reset(new Parser(" h1 #id. , h2 #id"));
t.reset(a->ParseSelectors());
EXPECT_FALSE(t.get());
EXPECT_TRUE(a->Done());
}
TEST_F(ParserTest, rulesets) {
scoped_ptr<Parser> a(new Parser("h1 p #id ;"));
scoped_ptr<Ruleset> t(a->ParseRuleset());
EXPECT_EQ(static_cast<Ruleset *>(NULL), t.get());
a.reset(new Parser(", { }"));
t.reset(a->ParseRuleset());
EXPECT_FALSE(t.get());
a.reset(new Parser(", h1 p #id, { };"));
t.reset(a->ParseRuleset());
EXPECT_FALSE(t.get());
a.reset(new Parser(
"h1 p + #id { font-size: 7px; width:10pt !important;}"));
t.reset(a->ParseRuleset());
ASSERT_EQ(1, (*t).selectors().size());
ASSERT_EQ(3, (*t).selector(0).size());
EXPECT_EQ(SimpleSelectors::SIBLING,
(*(*t).selectors()[0])[2]->combinator());
ASSERT_EQ(2, (*t).declarations().size());
EXPECT_EQ(false, (*t).declarations()[0]->IsImportant());
EXPECT_EQ(Property::WIDTH, (*t).declarations()[1]->prop());
EXPECT_EQ(true, (*t).declarations()[1]->IsImportant());
a.reset(new Parser("h1 p + #id , h1:first_child { font-size: 10px; }"));
t.reset(a->ParseRuleset());
ASSERT_EQ(2, (*t).selectors().size());
ASSERT_EQ(3, (*t).selector(0).size());
ASSERT_EQ(1, (*t).selector(1).size());
EXPECT_EQ(SimpleSelectors::SIBLING,
(*(*t).selectors()[0])[2]->combinator());
ASSERT_EQ(1, (*t).declarations().size());
EXPECT_EQ(false, (*t).declarations()[0]->IsImportant());
}
TEST_F(ParserTest, atrules) {
scoped_ptr<Parser> a(new Parser(
"@IMPORT url(assets/style.css) screen,printer"));
scoped_ptr<Stylesheet> t(new Stylesheet());
a->ParseAtrule(t.get());
ASSERT_EQ(1, (*t).imports().size());
EXPECT_EQ("assets/style.css", UnicodeTextToUTF8((*t).import(0).link));
EXPECT_EQ(true, a->Done());
a.reset(new Parser("@charset \"ISO-8859-1\" ;"));
t.reset(new Stylesheet());
a->ParseAtrule(t.get());
EXPECT_EQ(true, a->Done());
a.reset(new Parser(
"@media print,screen {\n\tbody { font-size: 10pt }\n}"));
t.reset(new Stylesheet());
a->ParseAtrule(t.get());
ASSERT_EQ(1, (*t).rulesets().size());
ASSERT_EQ(1, (*t).ruleset(0).selectors().size());
ASSERT_EQ(2, (*t).ruleset(0).media().size());
EXPECT_EQ("print", UnicodeTextToUTF8((*t).ruleset(0).medium(0)));
EXPECT_EQ("screen", UnicodeTextToUTF8((*t).ruleset(0).medium(1)));
ASSERT_EQ(1, (*(*t).ruleset(0).selectors()[0]).size());
EXPECT_EQ(kHtmlTagBody,
(*t).ruleset(0).selector(0)[0]->get(0)->element_type());
EXPECT_EQ(Property::FONT_SIZE,
(*t).ruleset(0).declarations()[0]->prop());
EXPECT_EQ(true, a->Done());
a.reset(new Parser(
"@page :left { margin-left: 4cm; margin-right: 3cm; }"));
t.reset(new Stylesheet());
a->ParseAtrule(t.get());
EXPECT_EQ(0, (*t).rulesets().size());
EXPECT_EQ(true, a->Done());
// Make sure media strings can be shared between multiple rulesets.
a.reset(new Parser(
"@media print { a { color: red; } p { color: blue; } }"));
t.reset(new Stylesheet());
a->ParseAtrule(t.get());
ASSERT_EQ(2, t->rulesets().size());
EXPECT_EQ("print", UnicodeTextToUTF8(t->ruleset(0).medium(0)));
EXPECT_EQ("print", UnicodeTextToUTF8(t->ruleset(1).medium(0)));
t->ToString(); // Make sure it can be written as a string.
}
TEST_F(ParserTest, stylesheets) {
scoped_ptr<Parser> a(new Parser("\n"
"\t@import \"mystyle.css\" all; "
"@import url(\"mystyle.css\" );\n"
"\tBODY {\n"
"color:black !important; \n"
"background: white !important; }\n"
"* {\n"
"\tcolor: inherit !important;\n"
"background: transparent;\n"
"}\n"
"\n"
"<!-- html comments * { font-size: 1 } -->\n"
"H1 + *[REL-up] {}"));
scoped_ptr<Stylesheet> t(a->ParseStylesheet());
EXPECT_EQ(Parser::kNoError, a->errors_seen_mask());
ASSERT_EQ(2, t->imports().size());
EXPECT_EQ("mystyle.css", UnicodeTextToUTF8(t->import(0).link));
ASSERT_EQ(1, t->import(0).media.size());
EXPECT_EQ("all", UnicodeTextToUTF8(t->import(0).media[0]));
EXPECT_EQ("mystyle.css", UnicodeTextToUTF8(t->import(1).link));
// html-style comment should NOT work
EXPECT_EQ(4, t->rulesets().size());
EXPECT_TRUE(a->Done());
}
TEST_F(ParserTest, ParseRawStylesheetDoesNotExpand) {
Parser p("a { background: none; }");
scoped_ptr<Stylesheet> stylesheet(p.ParseRawStylesheet());
ASSERT_EQ(1, stylesheet->rulesets().size());
ASSERT_EQ(1, stylesheet->ruleset(0).declarations().size());
ASSERT_EQ(1, stylesheet->ruleset(0).declaration(0).values()->size());
EXPECT_TRUE(p.Done());
// TODO(sligocki): add font, font-family.
}
TEST_F(ParserTest, ParseStylesheetDoesExpand) {
Parser p("a { background: none; }");
scoped_ptr<Stylesheet> stylesheet(p.ParseStylesheet());
ASSERT_EQ(1, stylesheet->rulesets().size());
const Declarations& declarations = stylesheet->ruleset(0).declarations();
ASSERT_EQ(7, declarations.size());
EXPECT_EQ(Property::BACKGROUND, declarations[0]->prop());
EXPECT_EQ(Property::BACKGROUND_COLOR, declarations[1]->prop());
EXPECT_EQ(Property::BACKGROUND_IMAGE, declarations[2]->prop());
EXPECT_EQ(Property::BACKGROUND_REPEAT, declarations[3]->prop());
EXPECT_EQ(Property::BACKGROUND_ATTACHMENT, declarations[4]->prop());
EXPECT_EQ(Property::BACKGROUND_POSITION_X, declarations[5]->prop());
EXPECT_EQ(Property::BACKGROUND_POSITION_Y, declarations[6]->prop());
EXPECT_TRUE(p.Done());
// TODO(sligocki): add font, font-family.
}
TEST_F(ParserTest, percentage_colors) {
Value hundred(100.0, Value::PERCENT);
EXPECT_EQ(255, Parser::ValueToRGB(&hundred));
Value zero(0.0, Value::PERCENT);
EXPECT_EQ(0, Parser::ValueToRGB(&zero));
}
TEST_F(ParserTest, value_equality) {
Value hundred(100.0, Value::PERCENT);
Value hundred2(100.0, Value::PERCENT);
Value zero(0.0, Value::PERCENT);
Identifier auto_ident(Identifier::AUTO);
Value ident(auto_ident);
EXPECT_TRUE(hundred.Equals(hundred2));
EXPECT_FALSE(hundred.Equals(zero));
EXPECT_FALSE(hundred.Equals(ident));
}
TEST_F(ParserTest, Utf8Error) {
Parser p("font-family: \"\xCB\xCE\xCC\xE5\"");
scoped_ptr<Declarations> declarations(p.ParseDeclarations());
EXPECT_EQ(1, declarations->size());
EXPECT_EQ(Parser::kUtf8Error, p.errors_seen_mask());
}
TEST_F(ParserTest, DeclarationError) {
Parser p("font-family ; ");
scoped_ptr<Declarations> declarations(p.ParseDeclarations());
EXPECT_EQ(0, declarations->size());
EXPECT_EQ(Parser::kDeclarationError, p.errors_seen_mask());
}
TEST_F(ParserTest, SelectorError) {
Parser p(".bold: { font-weight: bold }");
scoped_ptr<Stylesheet> stylesheet(p.ParseStylesheet());
EXPECT_EQ(0, stylesheet->rulesets().size());
EXPECT_EQ(Parser::kSelectorError, p.errors_seen_mask());
}
} // namespace