blob: f551256c5cfc5a7e8039e16d2765d1fc2d5559bc [file] [log] [blame]
# -*- coding: utf-8 -*-
require File.dirname(__FILE__) + '/test_helper'
require 'sass/scss/css_parser'
# These tests just test the parsing of CSS
# (both standard and any hacks we intend to support).
# Tests of SCSS-specific behavior go in scss_test.rb.
class ScssCssTest < MiniTest::Test
include ScssTestHelper
def test_basic_scss
assert_parses <<SCSS
selector {
property: value;
property2: value; }
SCSS
assert_equal <<CSS, render('sel{p:v}')
sel {
p: v; }
CSS
end
def test_empty_rule
assert_equal "", render("#foo .bar {}")
assert_equal "", render(<<SCSS)
#foo .bar {
}
SCSS
end
def test_cdo_and_cdc_ignored_at_toplevel
assert_equal <<CSS, render(<<SCSS)
foo {
bar: baz; }
bar {
bar: baz; }
baz {
bar: baz; }
CSS
foo {bar: baz}
<!--
bar {bar: baz}
-->
baz {bar: baz}
SCSS
end
def test_unicode
assert_parses <<SCSS
@charset "UTF-8";
foo {
bar: föö bâr; }
SCSS
assert_equal <<CSS, render(<<SCSS)
@charset "UTF-8";
foo {
bar: föö bâr; }
CSS
foo {
bar: föö bâr; }
SCSS
end
def test_invisible_comments
assert_equal <<CSS, render(<<SCSS)
foo {
a: d; }
CSS
foo {a: /* b; c: */ d}
SCSS
assert_equal <<CSS, render(<<SCSS)
foo {
a: d; }
CSS
foo {a /*: b; c */: d}
SCSS
end
def test_crazy_comments
# http://www.w3.org/Style/CSS/Test/CSS2.1/current/xhtml1/t040109-c17-comments-00-b.xht
assert_equal <<CSS, render(<<SCSS)
/* This is a CSS comment. */
.one {
color: green; }
/* Another comment */
/* The following should not be used:
.two {color: red;} */
.three {
color: green;
/* color: red; */ }
/**
.four {color: red;} */
.five {
color: green; }
/**/
.six {
color: green; }
/*********/
.seven {
color: green; }
/* a comment **/
.eight {
color: green; }
CSS
/* This is a CSS comment. */
.one {color: green;} /* Another comment */
/* The following should not be used:
.two {color: red;} */
.three {color: green; /* color: red; */}
/**
.four {color: red;} */
.five {color: green;}
/**/
.six {color: green;}
/*********/
.seven {color: green;}
/* a comment **/
.eight {color: green;}
SCSS
end
def test_rule_comments
assert_parses <<SCSS
/* Foo */
.foo {
a: b; }
SCSS
assert_equal <<CSS, render(<<SCSS)
/* Foo
* Bar */
.foo {
a: b; }
CSS
/* Foo
* Bar */.foo {
a: b; }
SCSS
end
def test_property_comments
assert_parses <<SCSS
.foo {
/* Foo */
a: b; }
SCSS
assert_equal <<CSS, render(<<SCSS)
.foo {
/* Foo
* Bar */
a: b; }
CSS
.foo {
/* Foo
* Bar */a: b; }
SCSS
end
def test_selector_comments
assert_equal <<CSS, render(<<SCSS)
.foo #bar:baz(bip) {
a: b; }
CSS
.foo /* .a #foo */ #bar:baz(/* bang )*/ bip) {
a: b; }
SCSS
end
def test_lonely_comments
assert_parses <<SCSS
/* Foo
* Bar */
SCSS
assert_parses <<SCSS
.foo {
/* Foo
* Bar */ }
SCSS
end
def test_multiple_comments
assert_parses <<SCSS
/* Foo
* Bar */
/* Baz
* Bang */
SCSS
assert_parses <<SCSS
.foo {
/* Foo
* Bar */
/* Baz
* Bang */ }
SCSS
assert_equal <<CSS, render(<<SCSS)
.foo {
/* Foo Bar */
/* Baz Bang */ }
CSS
.foo {
/* Foo Bar *//* Baz Bang */ }
SCSS
end
def test_bizarrely_formatted_comments
assert_parses <<SCSS
.foo {
/* Foo
Bar
Baz */
a: b; }
SCSS
assert_parses <<SCSS
.foo {
/* Foo
Bar
Baz */
a: b; }
SCSS
assert_equal <<CSS, render(<<SCSS)
.foo {
/* Foo
Bar */
a: b; }
CSS
.foo {/* Foo
Bar */
a: b; }
SCSS
assert_equal <<CSS, render(<<SCSS)
.foo {
/* Foo
Bar
Baz */
a: b; }
CSS
.foo {/* Foo
Bar
Baz */
a: b; }
SCSS
end
## Declarations
def test_vendor_properties
assert_parses <<SCSS
foo {
-moz-foo-bar: blat;
-o-flat-blang: wibble; }
SCSS
end
def test_empty_declarations
assert_equal <<CSS, render(<<SCSS)
foo {
bar: baz; }
CSS
foo {;;;;
bar: baz;;;;
;;}
SCSS
end
def test_basic_property_types
assert_parses <<SCSS
foo {
a: 2;
b: 2.3em;
c: 50%;
d: "fraz bran";
e: flanny-blanny-blan;
f: url(http://sass-lang.com);
g: U+ffa?;
h: #aabbcc; }
SCSS
end
def test_functions
assert_parses <<SCSS
foo {
a: foo-bar(12);
b: -foo-bar-baz(13, 14 15); }
SCSS
end
def test_unary_minus
assert_parses <<SCSS
foo {
a: -2;
b: -2.3em;
c: -50%;
d: -foo(bar baz); }
SCSS
end
def test_operators
assert_parses <<SCSS
foo {
a: foo bar baz;
b: foo, #aabbcc, -12;
c: 1px/2px/-3px;
d: foo bar, baz/bang; }
SCSS
end
def test_important
assert_parses <<SCSS
foo {
a: foo !important;
b: foo bar !important;
b: foo, bar !important; }
SCSS
end
def test_initial_hyphen
assert_parses <<SCSS
foo {
a: -moz-bar-baz;
b: foo -o-bar-baz; }
SCSS
end
def test_ms_long_filter_syntax
assert_equal <<CSS, render(<<SCSS)
foo {
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300, endColorstr=#ff000000);
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300, endColorstr=#ff000000); }
CSS
foo {
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300, endColorstr=#ff000000);
filter:progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300, endColorstr=#ff000000); }
SCSS
end
def test_ms_short_filter_syntax
assert_parses <<SCSS
foo {
filter: alpha(opacity=20);
filter: alpha(opacity=20, enabled=true);
filter: blaznicate(foo=bar, baz=bang bip, bart=#fa4600); }
SCSS
end
def test_declaration_hacks
assert_parses <<SCSS
foo {
_name: val;
*name: val;
:name: val;
.name: val;
#name: val;
name/**/: val;
name/*\\**/: val;
name: val; }
SCSS
end
def test_trailing_hash_hack
assert_parses <<SCSS
foo {
foo: bar;
#baz: bang;
#bip: bop; }
SCSS
end
def test_zero_arg_functions
assert_parses <<SCSS
foo {
a: foo();
b: bar baz-bang() bip; }
SCSS
end
def test_expression_function
assert_parses <<SCSS
foo {
a: 12px expression(1 + (3 / Foo.bar("baz" + "bang") + function() {return 12;}) % 12); }
SCSS
end
def test_calc_function
assert_parses <<SCSS
foo {
a: 12px calc(100%/3 - 2*1em - 2*1px);
b: 12px -moz-calc(100%/3 - 2*1em - 2*1px);
b: 12px -webkit-calc(100%/3 - 2*1em - 2*1px);
b: 12px -foobar-calc(100%/3 - 2*1em - 2*1px); }
SCSS
end
def test_element_function
assert_parses <<SCSS
foo {
a: -moz-element(#foo);
b: -webkit-element(#foo);
b: -foobar-element(#foo); }
SCSS
end
def test_unary_ops
assert_equal <<CSS, render(<<SCSS)
foo {
a: -0.5em;
b: +0.5em;
c: -foo(12px);
d: +foo(12px); }
CSS
foo {
a: -0.5em;
b: +0.5em;
c: -foo(12px);
d: +foo(12px); }
SCSS
end
def test_css_string_escapes
assert_parses <<SCSS
foo {
a: "\\foo bar";
b: "foo\\ bar";
c: "\\2022 \\0020";
d: "foo\\\\bar";
e: "foo\\"'bar"; }
SCSS
end
def test_css_ident_escapes
assert_parses <<SCSS
foo {
a: \\foo bar;
b: foo\\ bar;
c: \\2022 \\0020;
d: foo\\\\bar;
e: foo\\"\\'bar; }
SCSS
end
## Directives
def test_namespace_directive
assert_parses '@namespace "http://www.w3.org/Profiles/xhtml1-strict";'
assert_parses '@namespace url(http://www.w3.org/Profiles/xhtml1-strict);'
assert_parses '@namespace html url("http://www.w3.org/Profiles/xhtml1-strict");'
end
def test_media_directive
assert_parses <<SCSS
@media all {
rule1 {
prop: val; }
rule2 {
prop: val; } }
SCSS
assert_parses <<SCSS
@media screen, print {
rule1 {
prop: val; }
rule2 {
prop: val; } }
SCSS
end
def test_media_directive_with_keywords
assert_parses <<SCSS
@media screen and (-webkit-min-device-pixel-ratio: 0) {
a: b; }
SCSS
assert_parses <<SCSS
@media only screen, print and (foo: 0px) and (bar: flam(12px solid)) {
a: b; }
SCSS
end
def test_import_directive
assert_parses '@import "foo.css";'
assert_parses '@import url("foo.css");'
assert_parses "@import url('foo.css');"
assert_parses '@import url(foo.css);'
assert_equal <<CSS, render(<<SCSS)
@import "foo.css";
CSS
@import 'foo.css';
SCSS
end
def test_import_directive_with_backslash_newline
assert_equal <<CSS, render(<<SCSS)
@import "foobar.css";
CSS
@import "foo\\
bar.css";
SCSS
end
def test_string_import_directive_with_media
assert_parses '@import "foo.css" screen;'
assert_parses '@import "foo.css" screen, print;'
assert_parses '@import "foo.css" screen, print and (foo: 0);'
assert_parses '@import "foo.css" screen, only print, screen and (foo: 0);'
end
def test_url_import_directive_with_media
assert_parses '@import url("foo.css") screen;'
assert_parses '@import url("foo.css") screen, print;'
assert_parses '@import url("foo.css") screen, print and (foo: 0);'
assert_parses '@import url("foo.css") screen, only print, screen and (foo: 0);'
end
def test_page_directive
assert_parses <<SCSS
@page {
prop1: val;
prop2: val; }
SCSS
assert_parses <<SCSS
@page flap {
prop1: val;
prop2: val; }
SCSS
assert_parses <<SCSS
@page :first {
prop1: val;
prop2: val; }
SCSS
assert_parses <<SCSS
@page flap:first {
prop1: val;
prop2: val; }
SCSS
end
def test_blockless_directive_without_semicolon
assert_equal "@foo \"bar\";\n", render('@foo "bar"')
end
def test_directive_with_lots_of_whitespace
assert_equal "@foo \"bar\";\n", render('@foo "bar" ;')
end
def test_empty_blockless_directive
assert_parses "@foo;"
end
def test_multiple_blockless_directives
assert_parses <<SCSS
@foo bar;
@bar baz;
SCSS
end
def test_empty_block_directive
assert_parses "@foo {}"
assert_equal "@foo {}\n", render(<<SCSS)
@foo {
}
SCSS
end
def test_multiple_block_directives
assert_parses <<SCSS
@foo bar {
a: b; }
@bar baz {
c: d; }
SCSS
end
def test_block_directive_with_rule_and_property
assert_parses <<SCSS
@foo {
rule {
a: b; }
a: b; }
SCSS
end
def test_block_directive_with_semicolon
assert_equal <<CSS, render(<<SCSS)
@foo {
a: b; }
@bar {
a: b; }
CSS
@foo {a:b};
@bar {a:b};
SCSS
end
def test_moz_document_directive
assert_equal <<CSS, render(<<SCSS)
@-moz-document url(http://www.w3.org/),
url-prefix(http://www.w3.org/Style/),
domain(mozilla.org),
regexp("^https:.*") {
.foo {
a: b; } }
CSS
@-moz-document url(http://www.w3.org/),
url-prefix(http://www.w3.org/Style/),
domain(mozilla.org),
regexp("^https:.*") {
.foo {a: b}
}
SCSS
end
def test_supports
assert_equal <<CSS, render(<<SCSS)
@supports (((a: b) and (c: d)) or (not (d: e))) and ((not (f: g)) or (not ((h: i) and (j: k)))) {
.foo {
a: b; } }
@supports (a: b) {
.foo {
a: b; } }
CSS
@supports (((a: b) and (c: d)) or (not (d: e))) and ((not (f: g)) or (not ((h: i) and (j: k)))) {
.foo {
a: b;
}
}
@supports (a: b) {
.foo {
a: b;
}
}
SCSS
end
def test_supports_with_prefix
assert_equal <<CSS, render(<<SCSS)
@-prefix-supports (((a: b) and (c: d)) or (not (d: e))) and ((not (f: g)) or (not ((h: i) and (j: k)))) {
.foo {
a: b; } }
CSS
@-prefix-supports (((a: b) and (c: d)) or (not (d: e))) and ((not (f: g)) or (not ((h: i) and (j: k)))) {
.foo {
a: b;
}
}
SCSS
end
def test_supports_allows_similar_operators_without_parens
assert_equal <<CSS, render(<<SCSS)
@supports (a: b) and (c: d) and (e: f) {
.foo {
a: b; } }
@supports (a: b) or (c: d) or (e: f) {
.foo {
a: b; } }
CSS
@supports (a: b) and (c: d) and (e: f) {
.foo {
a: b;
}
}
@supports (a: b) or (c: d) or (e: f) {
.foo {
a: b;
}
}
SCSS
end
def test_keyframes
assert_equal <<CSS, render(<<SCSS)
@keyframes identifier {
0% {
top: 0;
left: 0; }
30% {
top: 50px; }
68%, 72% {
left: 50px; }
100% {
top: 100px;
left: 100%; } }
CSS
@keyframes identifier {
0% {top: 0; left: 0}
30% {top: 50px}
68%, 72% {left: 50px}
100% {top: 100px; left: 100%}
}
SCSS
end
def test_keyframes_with_empty_rules
assert_equal <<CSS, render(<<SCSS)
@keyframes identifier {
50% {
background-color: black; } }
CSS
@keyframes identifier {
0% {}
50% {background-color: black}
100% {}
}
SCSS
end
def test_keyframes_with_custom_identifiers
assert_equal <<CSS, render(<<SCSS)
@-skrollr-keyframes identifier {
center-top {
left: 100%; }
top-bottom {
left: 0%; } }
CSS
@-skrollr-keyframes identifier {
center-top {
left: 100%;
}
top-bottom {
left: 0%;
}
}
SCSS
end
## Selectors
# Taken from http://dev.w3.org/csswg/selectors4/#overview
def test_summarized_selectors_with_element
assert_selector_parses('*')
assert_selector_parses('E')
assert_selector_parses('E:not(s)')
assert_selector_parses('E:not(s1, s2)')
assert_selector_parses('E:matches(s1, s2)')
assert_selector_parses('E:has(s1, s2)')
assert_selector_parses('E:has(> s1, > s2)')
assert_selector_parses('E.warning')
assert_selector_parses('E#myid')
assert_selector_parses('E[foo]')
assert_selector_parses('E[foo="bar"]')
assert_selector_parses('E[foo="bar" i]')
assert_selector_parses('E[foo~="bar"]')
assert_selector_parses('E[foo^="bar"]')
assert_selector_parses('E[foo$="bar"]')
assert_selector_parses('E[foo*="bar"]')
assert_selector_parses('E[foo|="en"]')
assert_selector_parses('E:dir(ltr)')
assert_selector_parses('E:lang(fr)')
assert_selector_parses('E:lang(zh, *-hant)')
assert_selector_parses('E:any-link')
assert_selector_parses('E:link')
assert_selector_parses('E:visited')
assert_selector_parses('E:local-link')
assert_selector_parses('E:local-link(0)')
assert_selector_parses('E:target')
assert_selector_parses('E:scope')
assert_selector_parses('E:current')
assert_selector_parses('E:current(s)')
assert_selector_parses('E:past')
assert_selector_parses('E:future')
assert_selector_parses('E:active')
assert_selector_parses('E:hover')
assert_selector_parses('E:focus')
assert_selector_parses('E:enabled')
assert_selector_parses('E:disabled')
assert_selector_parses('E:checked')
assert_selector_parses('E:indeterminate')
assert_selector_parses('E:default')
assert_selector_parses('E:in-range')
assert_selector_parses('E:out-of-range')
assert_selector_parses('E:required')
assert_selector_parses('E:optional')
assert_selector_parses('E:read-only')
assert_selector_parses('E:read-write')
assert_selector_parses('E:root')
assert_selector_parses('E:empty')
assert_selector_parses('E:first-child')
assert_selector_parses('E:nth-child(n)')
assert_selector_parses('E:last-child')
assert_selector_parses('E:nth-last-child(n)')
assert_selector_parses('E:only-child')
assert_selector_parses('E:first-of-type')
assert_selector_parses('E:nth-of-type(n)')
assert_selector_parses('E:last-of-type')
assert_selector_parses('E:nth-last-of-type(n)')
assert_selector_parses('E:only-of-type')
assert_selector_parses('E:nth-child(n of selector)')
assert_selector_parses('E:nth-last-child(n of selector)')
assert_selector_parses('E:nth-child(n)')
assert_selector_parses('E:nth-last-child(n)')
assert_selector_parses('E F')
assert_selector_parses('E > F')
assert_selector_parses('E + F')
assert_selector_parses('E ~ F')
silence_warnings {assert_selector_parses('E /foo/ F')}
silence_warnings {assert_selector_parses('E! > F')}
silence_warnings {assert_selector_parses('E /ns|foo/ F')}
# From http://dev.w3.org/csswg/css-scoping-1/
assert_selector_parses('E:host(s)')
assert_selector_parses('E:host-context(s)')
end
# Taken from http://dev.w3.org/csswg/selectors4/#overview, but without element
# names.
def test_more_summarized_selectors
assert_selector_parses(':not(s)')
assert_selector_parses(':not(s1, s2)')
assert_selector_parses(':matches(s1, s2)')
assert_selector_parses(':has(s1, s2)')
assert_selector_parses(':has(> s1, > s2)')
assert_selector_parses('.warning')
assert_selector_parses('#myid')
assert_selector_parses('[foo]')
assert_selector_parses('[foo="bar"]')
assert_selector_parses('[foo="bar" i]')
assert_selector_parses('[foo~="bar"]')
assert_selector_parses('[foo^="bar"]')
assert_selector_parses('[foo$="bar"]')
assert_selector_parses('[foo*="bar"]')
assert_selector_parses('[foo|="en"]')
assert_selector_parses(':dir(ltr)')
assert_selector_parses(':lang(fr)')
assert_selector_parses(':lang(zh, *-hant)')
assert_selector_parses(':any-link')
assert_selector_parses(':link')
assert_selector_parses(':visited')
assert_selector_parses(':local-link')
assert_selector_parses(':local-link(0)')
assert_selector_parses(':target')
assert_selector_parses(':scope')
assert_selector_parses(':current')
assert_selector_parses(':current(s)')
assert_selector_parses(':past')
assert_selector_parses(':future')
assert_selector_parses(':active')
assert_selector_parses(':hover')
assert_selector_parses(':focus')
assert_selector_parses(':enabled')
assert_selector_parses(':disabled')
assert_selector_parses(':checked')
assert_selector_parses(':indeterminate')
assert_selector_parses(':default')
assert_selector_parses(':in-range')
assert_selector_parses(':out-of-range')
assert_selector_parses(':required')
assert_selector_parses(':optional')
assert_selector_parses(':read-only')
assert_selector_parses(':read-write')
assert_selector_parses(':root')
assert_selector_parses(':empty')
assert_selector_parses(':first-child')
assert_selector_parses(':nth-child(n)')
assert_selector_parses(':last-child')
assert_selector_parses(':nth-last-child(n)')
assert_selector_parses(':only-child')
assert_selector_parses(':first-of-type')
assert_selector_parses(':nth-of-type(n)')
assert_selector_parses(':last-of-type')
assert_selector_parses(':nth-last-of-type(n)')
assert_selector_parses(':only-of-type')
assert_selector_parses(':nth-child(n of selector)')
assert_selector_parses(':nth-last-child(n of selector)')
assert_selector_parses(':nth-child(n)')
assert_selector_parses(':nth-last-child(n)')
# From http://dev.w3.org/csswg/css-scoping-1/
assert_selector_parses(':host(s)')
assert_selector_parses(':host-context(s)')
end
def test_attribute_selectors_with_identifiers
assert_selector_parses('[foo~=bar]')
assert_selector_parses('[foo^=bar]')
assert_selector_parses('[foo$=bar]')
assert_selector_parses('[foo*=bar]')
assert_selector_parses('[foo|=en]')
end
def test_nth_selectors
assert_selector_parses(':nth-child(-n)')
assert_selector_parses(':nth-child(+n)')
assert_selector_parses(':nth-child(even)')
assert_selector_parses(':nth-child(odd)')
assert_selector_parses(':nth-child(50)')
assert_selector_parses(':nth-child(-50)')
assert_selector_parses(':nth-child(+50)')
assert_selector_parses(':nth-child(2n+3)')
assert_selector_parses(':nth-child(2n-3)')
assert_selector_parses(':nth-child(+2n-3)')
assert_selector_parses(':nth-child(-2n+3)')
assert_selector_parses(':nth-child(-2n+ 3)')
assert_equal(<<CSS, render(<<SCSS))
:nth-child(2n + 3) {
a: b; }
CSS
:nth-child( 2n + 3 ) {
a: b; }
SCSS
end
def test_selectors_containing_selectors
assert_selector_can_contain_selectors(':not(<sel>)')
assert_selector_can_contain_selectors(':current(<sel>)')
assert_selector_can_contain_selectors(':nth-child(n of <sel>)')
assert_selector_can_contain_selectors(':nth-last-child(n of <sel>)')
assert_selector_can_contain_selectors(':-moz-any(<sel>)')
assert_selector_can_contain_selectors(':has(<sel>)')
assert_selector_can_contain_selectors(':has(+ <sel>)')
assert_selector_can_contain_selectors(':host(<sel>)')
assert_selector_can_contain_selectors(':host-context(<sel>)')
end
def assert_selector_can_contain_selectors(sel)
try = lambda {|subsel| assert_selector_parses(sel.gsub('<sel>', subsel))}
try['foo|bar']
try['*|bar']
try['foo|*']
try['*|*']
try['#blah']
try['.blah']
try['[foo]']
try['[foo^="bar"]']
try['[baz|foo~="bar"]']
try[':hover']
try[':nth-child(2n + 3)']
try['h1, h2, h3']
try['#foo, bar, [baz]']
# Not technically allowed for most selectors, but what the heck
try[':not(#foo)']
try['a#foo.bar']
try['#foo .bar > baz']
end
def test_namespaced_selectors
assert_selector_parses('foo|E')
assert_selector_parses('*|E')
assert_selector_parses('foo|*')
assert_selector_parses('*|*')
end
def test_namespaced_attribute_selectors
assert_selector_parses('[foo|bar=baz]')
assert_selector_parses('[*|bar=baz]')
assert_selector_parses('[foo|bar|=baz]')
end
def test_comma_selectors
assert_selector_parses('E, F')
assert_selector_parses('E F, G H')
assert_selector_parses('E > F, G > H')
end
def test_selectors_with_newlines
assert_selector_parses("E,\nF")
assert_selector_parses("E\nF")
assert_selector_parses("E, F\nG, H")
end
def test_expression_fallback_selectors
assert_directive_parses('0%')
assert_directive_parses('60%')
assert_directive_parses('100%')
assert_directive_parses('12px')
assert_directive_parses('"foo"')
end
def test_functional_pseudo_selectors
assert_selector_parses(':foo("bar")')
assert_selector_parses(':foo(bar)')
assert_selector_parses(':foo(12px)')
assert_selector_parses(':foo(+)')
assert_selector_parses(':foo(-)')
assert_selector_parses(':foo(+"bar")')
assert_selector_parses(':foo(-++--baz-"bar"12px)')
end
def test_selector_hacks
assert_selector_parses('> E')
assert_selector_parses('+ E')
assert_selector_parses('~ E')
assert_selector_parses('> > E')
assert_equal <<CSS, render(<<SCSS)
> > E {
a: b; }
CSS
>> E {
a: b; }
SCSS
assert_selector_parses('E*')
assert_selector_parses('E*.foo')
assert_selector_parses('E*:hover')
end
def test_spaceless_combo_selectors
assert_equal "E > F {\n a: b; }\n", render("E>F { a: b;} ")
assert_equal "E ~ F {\n a: b; }\n", render("E~F { a: b;} ")
assert_equal "E + F {\n a: b; }\n", render("E+F { a: b;} ")
end
def test_escapes_in_selectors
assert_selector_parses('.\!foo')
assert_selector_parses('.\66 foo')
assert_selector_parses('.\21 foo')
end
def test_subject_selector_deprecation
assert_warning(<<WARNING) {render(".foo .bar! .baz {a: b}")}
DEPRECATION WARNING on line 1, column 1:
The subject selector operator "!" is deprecated and will be removed in a future release.
This operator has been replaced by ":has()" in the CSS spec.
For example: .foo .bar:has(.baz)
WARNING
assert_warning(<<WARNING) {render(".foo .bar! > .baz {a: b}")}
DEPRECATION WARNING on line 1, column 1:
The subject selector operator "!" is deprecated and will be removed in a future release.
This operator has been replaced by ":has()" in the CSS spec.
For example: .foo .bar:has(> .baz)
WARNING
assert_warning(<<WARNING) {render(".foo .bar! {a: b}")}
DEPRECATION WARNING on line 1, column 1:
The subject selector operator "!" is deprecated and will be removed in a future release.
This operator has been replaced by ":has()" in the CSS spec.
For example: .foo .bar
WARNING
end
## Errors
def test_invalid_directives
assert_not_parses("identifier", '@<err> import "foo";')
assert_not_parses("identifier", '@<err>12 "foo";')
end
def test_invalid_classes
assert_not_parses("class name", 'p.<err> foo {a: b}')
assert_not_parses("class name", 'p.<err>1foo {a: b}')
end
def test_invalid_ids
assert_not_parses("id name", 'p#<err> foo {a: b}')
end
def test_no_properties_at_toplevel
assert_not_parses('pseudoclass or pseudoelement', 'a:<err> b;')
end
def test_no_scss_directives
assert_parses('@import "foo.sass";')
assert_parses <<SCSS
@mixin foo {
a: b; }
SCSS
end
def test_no_variables
assert_not_parses("selector or at-rule", "<err>$var = 12;")
assert_not_parses('"}"', "foo { <err>!var = 12; }")
end
def test_no_parent_selectors
assert_not_parses('"{"', "foo <err>&.bar {a: b}")
end
def test_no_selector_interpolation
assert_not_parses('"{"', 'foo <err>#{"bar"}.baz {a: b}')
end
def test_no_prop_name_interpolation
assert_not_parses('":"', 'foo {a<err>#{"bar"}baz: b}')
end
def test_no_prop_val_interpolation
assert_not_parses('"}"', 'foo {a: b <err>#{"bar"} c}')
end
def test_no_string_interpolation
assert_parses <<SCSS
foo {
a: "bang \#{1 + " bar "} bip"; }
SCSS
end
def test_no_sass_script_values
assert_not_parses('"}"', 'foo {a: b <err>* c}')
end
def test_no_nested_rules
assert_not_parses('":"', 'foo {bar <err>{a: b}}')
assert_not_parses('"}"', 'foo {<err>[bar=baz] {a: b}}')
end
def test_no_nested_properties
assert_not_parses('expression (e.g. 1px, bold)', 'foo {bar: <err>{a: b}}')
assert_not_parses('expression (e.g. 1px, bold)', 'foo {bar: bang <err>{a: b}}')
end
def test_no_nested_directives
assert_not_parses('"}"', 'foo {<err>@bar {a: b}}')
end
def test_error_with_windows_newlines
render <<SCSS
foo {bar}\r
baz {a: b}
SCSS
assert(false, "Expected syntax error")
rescue Sass::SyntaxError => e
assert_equal 'Invalid CSS after "foo {bar": expected ":", was "}"', e.message
assert_equal 1, e.sass_line
end
def test_newline_in_property_value
assert_equal(<<CSS, render(<<SCSS))
.foo {
bar: "bazbang"; }
CSS
.foo {
bar: "baz\\
bang";
}
SCSS
end
## Regressions
def test_very_long_comment_doesnt_take_forever
string = 'asdf' * (100000)
assert_equal(<<CSS, render(<<SCSS))
/*
#{string}
*/
CSS
/*
#{string}
*/
SCSS
end
def test_long_unclosed_comment_doesnt_take_forever
assert_raise_message(Sass::SyntaxError,
'Invalid CSS after "/*": expected "/", was "//*************..."') {render(<<SCSS)}
/*
//**************************************************************************
SCSS
end
def test_double_space_string
assert_equal(<<CSS, render(<<SCSS))
.a {
content: " a"; }
CSS
.a {
content: " a";
}
SCSS
end
def test_very_long_number_with_important_doesnt_take_forever
assert_equal(<<CSS, render(<<SCSS))
.foo {
width: 97.916666666666666666666666666667% !important; }
CSS
.foo {
width: 97.916666666666666666666666666667% !important;
}
SCSS
end
def test_selector_without_closing_bracket
assert_not_parses('"]"', "foo[bar <err>{a: b}")
end
def test_closing_line_comment_end_with_compact_output
assert_equal(<<CSS, render(<<SCSS, :style => :compact))
/* foo */
bar { baz: bang; }
CSS
/*
* foo
*/
bar {baz: bang}
SCSS
end
def test_single_line_comment_within_multiline_comment
assert_equal(<<CSS, render(<<SCSS))
body {
/*
//comment here
*/ }
CSS
body {
/*
//comment here
*/
}
SCSS
end
def test_malformed_media
render <<SCSS
@media {
margin: 0;
}
SCSS
assert(false, "Expected syntax error")
rescue Sass::SyntaxError => e
assert_equal 'Invalid CSS after "@media ": expected media query (e.g. print, screen, print and screen), was "{"', e.message
assert_equal 1, e.sass_line
end
private
def assert_selector_parses(selector)
assert_parses <<SCSS
#{selector} {
a: b; }
SCSS
assert_parses <<SCSS
:not(#{selector}) {
a: b; }
SCSS
end
def assert_directive_parses(param)
assert_parses <<SCSS
@unknown #{param} {
a: b; }
SCSS
end
def render(scss, options = {})
tree = Sass::SCSS::CssParser.new(scss, options[:filename], nil).parse
tree.options = Sass::Engine::DEFAULT_OPTIONS.merge(options)
tree.render
end
end