JezK
Edit File: css_test.rb
#!/usr/bin/env ruby # -*- 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 if Sass::Util.ruby1_8? def test_unicode assert_parses <<SCSS @charset "UTF-8"; foo { bar: föö bâr; } SCSS assert_parses <<SCSS foo { bar: föö bâr; } SCSS end else 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 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 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_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') assert_selector_parses('E /foo/ F') silence_warnings {assert_selector_parses('E! > F')} 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