extern crate azul_simplecss;
use azul_simplecss::{Tokenizer, Token, Combinator, Error, ErrorPos};
macro_rules! test {
($name:ident, $text:expr, $( $token:expr ),*) => {
#[test]
fn $name() {
let mut t = Tokenizer::new($text);
$(
assert_eq!(t.parse_next().unwrap(), $token);
)*
assert_eq!(t.parse_next().unwrap(), Token::EndOfStream);
}
};
}
macro_rules! test_selectors {
($name:ident, $text:expr, $( $token:expr ),*) => {
#[test]
fn $name() {
let mut t = Tokenizer::new($text);
$(
assert_eq!(t.parse_next().unwrap(), $token);
)*
assert_eq!(t.parse_next().unwrap(), Token::BlockStart);
assert_eq!(t.parse_next().unwrap(), Token::Declaration("color", "red"));
assert_eq!(t.parse_next().unwrap(), Token::BlockEnd);
assert_eq!(t.parse_next().unwrap(), Token::EndOfStream);
}
};
}
macro_rules! test_err {
($name:ident, $text:expr, $err:expr) => {
#[test]
fn $name() {
let mut t = Tokenizer::new($text);
assert_eq!(t.parse_next().unwrap_err(), $err);
}
};
}
test_selectors!(selectors_1,
"* { color: red }",
Token::UniversalSelector
);
test_selectors!(selectors_2,
"p { color: red }",
Token::TypeSelector("p")
);
test_selectors!(selectors_3,
":first-child { color: red }",
Token::PseudoClass { selector: "first-child", value: None }
);
test_selectors!(selectors_4,
":lang(fr) { color: red }",
Token::PseudoClass { selector: "lang", value: Some("fr") }
);
test_selectors!(selectors_6,
".cls { color: red }",
Token::ClassSelector("cls")
);
test_selectors!(selectors_7,
"#p2 { color: red }",
Token::IdSelector("p2")
);
test_selectors!(selectors_8,
"#p2{color:red}",
Token::IdSelector("p2")
);
test_selectors!(selectors_9,
" div { color:red }",
Token::TypeSelector("div")
);
test_selectors!(complex_selectors_1,
"h1 p { color: red; }",
Token::TypeSelector("h1"),
Token::Combinator(Combinator::Space),
Token::TypeSelector("p")
);
test_selectors!(complex_selectors_2,
"h1 p g k { color: red; }",
Token::TypeSelector("h1"),
Token::Combinator(Combinator::Space),
Token::TypeSelector("p"),
Token::Combinator(Combinator::Space),
Token::TypeSelector("g"),
Token::Combinator(Combinator::Space),
Token::TypeSelector("k")
);
test_selectors!(complex_selectors_3,
"[rel=\"author\"], [rel=\"alternate\"] { color: red; }",
Token::AttributeSelector("rel=\"author\""),
Token::Comma,
Token::AttributeSelector("rel=\"alternate\"")
);
test_selectors!(complex_selectors_4,
"div:after, div:before { color: red; }",
Token::TypeSelector("div"),
Token::PseudoClass { selector: "after", value: None },
Token::Comma,
Token::TypeSelector("div"),
Token::PseudoClass { selector: "before", value: None }
);
test_selectors!(complex_selectors_5,
"p.valid { color: red; }",
Token::TypeSelector("p"),
Token::ClassSelector("valid")
);
test_selectors!(complex_selectors_6,
".test:first-letter { color: red; }",
Token::ClassSelector("test"),
Token::PseudoClass { selector: "first-letter", value: None }
);
test_selectors!(complex_selectors_7,
".test, .control { color: red; }",
Token::ClassSelector("test"),
Token::Comma,
Token::ClassSelector("control")
);
test_selectors!(complex_selectors_8,
"div>h1 { color: red; }",
Token::TypeSelector("div"),
Token::Combinator(Combinator::GreaterThan),
Token::TypeSelector("h1")
);
test_selectors!(complex_selectors_9,
"div > h1 { color: red; }",
Token::TypeSelector("div"),
Token::Combinator(Combinator::GreaterThan),
Token::TypeSelector("h1")
);
test_selectors!(complex_selectors_10,
"div+h1 { color: red; }",
Token::TypeSelector("div"),
Token::Combinator(Combinator::Plus),
Token::TypeSelector("h1")
);
test_selectors!(complex_selectors_11,
"div+h1 { color: red; }",
Token::TypeSelector("div"),
Token::Combinator(Combinator::Plus),
Token::TypeSelector("h1")
);
test_selectors!(complex_selectors_12,
"p.test:first-letter { color: red; }",
Token::TypeSelector("p"),
Token::ClassSelector("test"),
Token::PseudoClass { selector: "first-letter", value: None }
);
test_selectors!(complex_selectors_13,
"#div1
+
p { color: red; }",
Token::IdSelector("div1"),
Token::Combinator(Combinator::Plus),
Token::TypeSelector("p")
);
test_selectors!(complex_selectors_14,
"button[type=\"submit\"] { color: red; }",
Token::TypeSelector("button"),
Token::AttributeSelector("type=\"submit\"")
);
test_selectors!(complex_selectors_15,
"div em[id] { color: red; }",
Token::TypeSelector("div"),
Token::Combinator(Combinator::Space),
Token::TypeSelector("em"),
Token::AttributeSelector("id")
);
test_selectors!(complex_selectors_16,
"div * em { color: red; }",
Token::TypeSelector("div"),
Token::UniversalSelector,
Token::TypeSelector("em")
);
test_selectors!(complex_selectors_17,
"div#div1 { color: red; }",
Token::TypeSelector("div"),
Token::IdSelector("div1")
);
test_selectors!(complex_selectors_18,
"div#x:first-letter { color: red; }",
Token::TypeSelector("div"),
Token::IdSelector("x"),
Token::PseudoClass { selector: "first-letter", value: None }
);
test_selectors!(complex_selectors_19,
"[class=foo] + div + div + div + div { color: red; }",
Token::AttributeSelector("class=foo"),
Token::Combinator(Combinator::Plus),
Token::TypeSelector("div"),
Token::Combinator(Combinator::Plus),
Token::TypeSelector("div"),
Token::Combinator(Combinator::Plus),
Token::TypeSelector("div"),
Token::Combinator(Combinator::Plus),
Token::TypeSelector("div")
);
test_selectors!(complex_selectors_20,
"input[type=\"radio\"]:focus + label { color: red; }",
Token::TypeSelector("input"),
Token::AttributeSelector("type=\"radio\""),
Token::PseudoClass { selector: "focus", value: None },
Token::Combinator(Combinator::Plus),
Token::TypeSelector("label")
);
test_selectors!(complex_selectors_21,
":visited:active { color: red; }",
Token::PseudoClass { selector: "visited", value: None },
Token::PseudoClass { selector: "active", value: None }
);
test_selectors!(complex_selectors_22,
"p:first-line p, #p1 { color: red; }",
Token::TypeSelector("p"),
Token::PseudoClass { selector: "first-line", value: None },
Token::Combinator(Combinator::Space),
Token::TypeSelector("p"),
Token::Comma,
Token::IdSelector("p1")
);
test_selectors!(complex_selectors_23,
"p * { color: red; }",
Token::TypeSelector("p"),
Token::UniversalSelector
);
test_selectors!(complex_selectors_24,
"*:active { color: red; }",
Token::UniversalSelector,
Token::PseudoClass { selector: "active", value: None }
);
test_selectors!(complex_selectors_25,
"html > body > *:first-line { color: red; }",
Token::TypeSelector("html"),
Token::Combinator(Combinator::GreaterThan),
Token::TypeSelector("body"),
Token::Combinator(Combinator::GreaterThan),
Token::UniversalSelector,
Token::PseudoClass { selector: "first-line", value: None }
);
test_selectors!(complex_selectors_26,
"@keyframes mymove { color: red }",
Token::AtRule("keyframes"),
Token::AtStr("mymove")
);
test_selectors!(attribute_selector_1,
"[attr=\"test\"] { color: red }",
Token::AttributeSelector("attr=\"test\"")
);
test_selectors!(attribute_selector_2,
"[attr=\"test\"][attr2=\"test2\"] { color: red }",
Token::AttributeSelector("attr=\"test\""),
Token::AttributeSelector("attr2=\"test2\"")
);
test!(blocks_1,
"p { color: red; }
p { color: red; }",
Token::TypeSelector("p"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd,
Token::TypeSelector("p"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(blocks_2,
"p{color:red;}p{color:red;}",
Token::TypeSelector("p"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd,
Token::TypeSelector("p"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(blocks_3,
"p {
color:red;
}",
Token::TypeSelector("p"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(blocks_4,
"p
{
color:red;
}",
Token::TypeSelector("p"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(blocks_5,
"p{}",
Token::TypeSelector("p"),
Token::BlockStart,
Token::BlockEnd
);
test!(blocks_6,
"@keyframes hello { from { width: 500px; } to { width: 600px; } }",
Token::AtRule("keyframes"),
Token::AtStr("hello"),
Token::BlockStart,
Token::DeclarationStr("from"),
Token::BlockStart,
Token::Declaration("width", "500px"),
Token::BlockEnd,
Token::DeclarationStr("to"),
Token::BlockStart,
Token::Declaration("width", "600px"),
Token::BlockEnd,
Token::BlockEnd
);
#[test]
fn declarations_1() {
let vec = vec![
"p {color:red}",
"p {color:red;}",
"p {color:red }",
"p { color: red; }",
"p { color : red ; }",
"p { color : red ; } ",
"p { color : red ; }"
];
for css in vec {
let mut t = Tokenizer::new(css);
assert_eq!(t.parse_next().unwrap(), Token::TypeSelector("p"));
assert_eq!(t.parse_next().unwrap(), Token::BlockStart);
assert_eq!(t.parse_next().unwrap(), Token::Declaration("color", "red"));
assert_eq!(t.parse_next().unwrap(), Token::BlockEnd);
assert_eq!(t.parse_next().unwrap(), Token::EndOfStream);
}
}
test!(declarations_2,
"p { color:red;;;;color:red; }",
Token::TypeSelector("p"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(declarations_3,
"* {list-style-image: url(\"img.png\");}",
Token::UniversalSelector,
Token::BlockStart,
Token::Declaration("list-style-image", "url(\"img.png\")"),
Token::BlockEnd
);
test!(declarations_4,
"* { color: white ! important; }",
Token::UniversalSelector,
Token::BlockStart,
Token::Declaration("color", "white ! important"),
Token::BlockEnd
);
test!(declarations_5,
"* { border: 1em solid blue; background: navy url(support/diamond.png) -2em -2em no-repeat }",
Token::UniversalSelector,
Token::BlockStart,
Token::Declaration("border", "1em solid blue"),
Token::Declaration("background", "navy url(support/diamond.png) -2em -2em no-repeat"),
Token::BlockEnd
);
test!(declarations_6,
"* {stroke-width:2}",
Token::UniversalSelector,
Token::BlockStart,
Token::Declaration("stroke-width", "2"),
Token::BlockEnd
);
test!(comment_1,
"/* .test { color: green ! important; } */
* { color: red; }",
Token::UniversalSelector,
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(comment_2,
"p /* comment */ { color:red }",
Token::TypeSelector("p"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(comment_3,
"p /* comment */ div { color:red }",
Token::TypeSelector("p"),
Token::Combinator(Combinator::Space),
Token::TypeSelector("div"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(comment_4,
"div { /**/color: red; }",
Token::TypeSelector("div"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(comment_5,
"div { /**/color: red; }",
Token::TypeSelector("div"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(comment_6,
"div { /* *\\/*/color: red; }",
Token::TypeSelector("div"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(comment_7,
"/*Comment*/div/*Comment*/
{
/*Comment*/color/*Comment*/: /*Comment*/red;
/*Comment*/
}/*Comment*/",
Token::TypeSelector("div"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test!(comment_8,
" /*
* Comment
*/
div
{
color : red
}",
Token::TypeSelector("div"),
Token::BlockStart,
Token::Declaration("color", "red"),
Token::BlockEnd
);
test_err!(invalid_2,
"# div1",
Error::UnknownToken(ErrorPos::new(1, 2))
);
test!(invalid_4,
"@import",
Token::AtRule("import")
);
#[test]
fn invalid_5() {
let mut t = Tokenizer::new("div { {color: red;} }");
assert_eq!(t.parse_next().unwrap(), Token::TypeSelector("div"));
assert_eq!(t.parse_next().unwrap(), Token::BlockStart);
assert_eq!(t.parse_next().unwrap(), Token::BlockStart); }
#[test]
fn invalid_6() {
let mut t = Tokenizer::new("div { (color: red;) }");
assert_eq!(t.parse_next().unwrap(), Token::TypeSelector("div"));
assert_eq!(t.parse_next().unwrap(), Token::BlockStart);
assert_eq!(t.parse_next().unwrap_err(), Error::UnknownToken(ErrorPos::new(1, 7)));
}
#[test]
fn invalid_7() {
let mut t = Tokenizer::new("div { [color: red;] }");
assert_eq!(t.parse_next().unwrap(), Token::TypeSelector("div"));
assert_eq!(t.parse_next().unwrap(), Token::BlockStart);
assert_eq!(t.parse_next().unwrap_err(), Error::UnknownToken(ErrorPos::new(1, 7)));
}
#[test]
fn invalid_8() {
let mut t = Tokenizer::new("div { color: }");
assert_eq!(t.parse_next().unwrap(), Token::TypeSelector("div"));
assert_eq!(t.parse_next().unwrap(), Token::BlockStart);
assert_eq!(t.parse_next().unwrap_err(), Error::UnknownToken(ErrorPos::new(1, 14)));
}
#[test]
fn invalid_9() {
let mut t = Tokenizer::new("div");
assert_eq!(t.parse_next().unwrap(), Token::TypeSelector("div"));
assert_eq!(t.parse_next().unwrap(), Token::EndOfStream);
}
#[test]
fn invalid_10() {
let mut t = Tokenizer::new("div { /\\*;color: green;*/ }");
assert_eq!(t.parse_next().unwrap(), Token::TypeSelector("div"));
assert_eq!(t.parse_next().unwrap(), Token::BlockStart);
assert_eq!(t.parse_next().unwrap_err(), Error::UnknownToken(ErrorPos::new(1, 8)));
}
#[test]
fn invalid_11() {
let mut t = Tokenizer::new("div { /*\\*/*/color: red; }");
assert_eq!(t.parse_next().unwrap(), Token::TypeSelector("div"));
assert_eq!(t.parse_next().unwrap(), Token::BlockStart);
assert_eq!(t.parse_next().unwrap_err(), Error::UnknownToken(ErrorPos::new(1, 12)));
}
test_err!(invalid_12,
".平和 { color: red; }",
Error::UnknownToken(ErrorPos::new(1, 2))
);
#[test]
fn invalid_14() {
let mut t = Tokenizer::new(
"div
{
\"this is a string]}\"\"[{\\\"'\"; /*should be parsed as a string but be ignored*/
{{}}[]''; /*should be parsed as nested blocks and a string but be ignored*/
color: green;
}");
assert_eq!(t.parse_next().unwrap(), Token::TypeSelector("div"));
assert_eq!(t.parse_next().unwrap(), Token::BlockStart);
assert_eq!(t.parse_next().unwrap_err(), Error::UnknownToken(ErrorPos::new(3, 5)));
}
test_err!(invalid_15,
".\\xC3\\xA9 { color: red; }",
Error::UnknownToken(ErrorPos::new(1, 2))
);
test!(invalid_16,
"::invalidPseudoElement",
Token::DoublePseudoClass { selector: "invalidPseudoElement", value: None }
);
#[test]
fn invalid_17() {
let mut t = Tokenizer::new(" ");
assert_eq!(t.parse_next().unwrap(), Token::EndOfStream);
}
#[test]
fn invalid_18() {
let mut t = Tokenizer::new("div > >");
assert_eq!(t.parse_next().unwrap(), Token::TypeSelector("div"));
assert_eq!(t.parse_next().unwrap(), Token::Combinator(Combinator::GreaterThan));
assert_eq!(t.parse_next().unwrap_err(), Error::UnknownToken(ErrorPos::new(1, 7)));
}