| # -*- coding: utf-8 -*- |
| require File.dirname(__FILE__) + '/test_helper' |
| |
| class ScssTest < MiniTest::Test |
| include ScssTestHelper |
| |
| ## One-Line Comments |
| |
| def test_one_line_comments |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| baz: bang; } |
| CSS |
| .foo {// bar: baz;} |
| baz: bang; //} |
| } |
| SCSS |
| assert_equal <<CSS, render(<<SCSS) |
| .foo bar[val="//"] { |
| baz: bang; } |
| CSS |
| .foo bar[val="//"] { |
| baz: bang; //} |
| } |
| SCSS |
| end |
| |
| ## Script |
| |
| def test_variables |
| assert_equal <<CSS, render(<<SCSS) |
| blat { |
| a: foo; } |
| CSS |
| $var: foo; |
| |
| blat {a: $var} |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: 2; |
| b: 6; } |
| CSS |
| foo { |
| $var: 2; |
| $another-var: 4; |
| a: $var; |
| b: $var + $another-var;} |
| SCSS |
| end |
| |
| def test_unicode_variables |
| assert_equal <<CSS, render(<<SCSS) |
| blat { |
| a: foo; } |
| CSS |
| $vär: foo; |
| |
| blat {a: $vär} |
| SCSS |
| end |
| |
| def test_guard_assign |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: 1; } |
| CSS |
| $var: 1; |
| $var: 2 !default; |
| |
| foo {a: $var} |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: 2; } |
| CSS |
| $var: 2 !default; |
| |
| foo {a: $var} |
| SCSS |
| end |
| |
| def test_sass_script |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: 3; |
| b: -1; |
| c: foobar; |
| d: 12px; } |
| CSS |
| foo { |
| a: 1 + 2; |
| b: 1 - 2; |
| c: foo + bar; |
| d: floor(12.3px); } |
| SCSS |
| end |
| |
| def test_debug_directive |
| assert_warning "test_debug_directive_inline.scss:2 DEBUG: hello world!" do |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: b; } |
| |
| bar { |
| c: d; } |
| CSS |
| foo {a: b} |
| @debug "hello world!"; |
| bar {c: d} |
| SCSS |
| end |
| end |
| |
| def test_error_directive |
| assert_raise_message(Sass::SyntaxError, "hello world!") {render(<<SCSS)} |
| foo {a: b} |
| @error "hello world!"; |
| bar {c: d} |
| SCSS |
| end |
| |
| def test_warn_directive |
| expected_warning = <<EXPECTATION |
| WARNING: this is a warning |
| on line 2 of test_warn_directive_inline.scss |
| |
| WARNING: this is a mixin |
| on line 1 of test_warn_directive_inline.scss, in `foo' |
| from line 3 of test_warn_directive_inline.scss |
| EXPECTATION |
| assert_warning expected_warning do |
| assert_equal <<CSS, render(<<SCSS) |
| bar { |
| c: d; } |
| CSS |
| @mixin foo { @warn "this is a mixin";} |
| @warn "this is a warning"; |
| bar {c: d; @include foo;} |
| SCSS |
| end |
| end |
| |
| def test_for_directive |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| a: 2; |
| a: 3; |
| a: 4; } |
| CSS |
| .foo { |
| @for $var from 1 to 5 {a: $var;} |
| } |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| a: 2; |
| a: 3; |
| a: 4; |
| a: 5; } |
| CSS |
| .foo { |
| @for $var from 1 through 5 {a: $var;} |
| } |
| SCSS |
| end |
| |
| def test_for_directive_with_same_start_and_end |
| assert_equal <<CSS, render(<<SCSS) |
| CSS |
| .foo { |
| @for $var from 1 to 1 {a: $var;} |
| } |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; } |
| CSS |
| .foo { |
| @for $var from 1 through 1 {a: $var;} |
| } |
| SCSS |
| end |
| |
| def test_decrementing_estfor_directive |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 5; |
| a: 4; |
| a: 3; |
| a: 2; |
| a: 1; } |
| CSS |
| .foo { |
| @for $var from 5 through 1 {a: $var;} |
| } |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 5; |
| a: 4; |
| a: 3; |
| a: 2; } |
| CSS |
| .foo { |
| @for $var from 5 to 1 {a: $var;} |
| } |
| SCSS |
| end |
| |
| def test_if_directive |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: b; } |
| CSS |
| @if "foo" == "foo" {foo {a: b}} |
| @if "foo" != "foo" {bar {a: b}} |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| bar { |
| a: b; } |
| CSS |
| @if "foo" != "foo" {foo {a: b}} |
| @else if "foo" == "foo" {bar {a: b}} |
| @else if true {baz {a: b}} |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| bar { |
| a: b; } |
| CSS |
| @if "foo" != "foo" {foo {a: b}} |
| @else {bar {a: b}} |
| SCSS |
| end |
| |
| def test_comment_after_if_directive |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: b; |
| /* This is a comment */ |
| c: d; } |
| CSS |
| foo { |
| @if true {a: b} |
| /* This is a comment */ |
| c: d } |
| SCSS |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: b; |
| /* This is a comment */ |
| c: d; } |
| CSS |
| foo { |
| @if true {a: b} |
| @else {x: y} |
| /* This is a comment */ |
| c: d } |
| SCSS |
| end |
| |
| def test_while_directive |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| a: 2; |
| a: 3; |
| a: 4; } |
| CSS |
| $i: 1; |
| |
| .foo { |
| @while $i != 5 { |
| a: $i; |
| $i: $i + 1 !global; |
| } |
| } |
| SCSS |
| end |
| |
| def test_each_directive |
| assert_equal <<CSS, render(<<SCSS) |
| a { |
| b: 1px; |
| b: 2px; |
| b: 3px; |
| b: 4px; } |
| |
| c { |
| d: foo; |
| d: bar; |
| d: baz; |
| d: bang; } |
| CSS |
| a { |
| @each $number in 1px 2px 3px 4px { |
| b: $number; |
| } |
| } |
| c { |
| @each $str in foo, bar, baz, bang { |
| d: $str; |
| } |
| } |
| SCSS |
| end |
| |
| def test_destructuring_each_directive |
| assert_equal <<CSS, render(<<SCSS) |
| a { |
| foo: 1px; |
| bar: 2px; |
| baz: 3px; } |
| |
| c { |
| foo: "Value is bar"; |
| bar: "Value is baz"; |
| bang: "Value is "; } |
| CSS |
| a { |
| @each $name, $number in (foo: 1px, bar: 2px, baz: 3px) { |
| \#{$name}: $number; |
| } |
| } |
| c { |
| @each $key, $value in (foo bar) (bar, baz) bang { |
| \#{$key}: "Value is \#{$value}"; |
| } |
| } |
| SCSS |
| end |
| |
| def test_css_import_directive |
| assert_equal "@import url(foo.css);\n", render('@import "foo.css";') |
| assert_equal "@import url(foo.css);\n", render("@import 'foo.css';") |
| assert_equal "@import url(\"foo.css\");\n", render('@import url("foo.css");') |
| assert_equal "@import url(\"foo.css\");\n", render('@import url("foo.css");') |
| assert_equal "@import url(foo.css);\n", render('@import url(foo.css);') |
| end |
| |
| def test_css_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_css_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_media_import |
| assert_equal("@import \"./fonts.sass\" all;\n", render("@import \"./fonts.sass\" all;")) |
| end |
| |
| def test_dynamic_media_import |
| assert_equal(<<CSS, render(<<SCSS)) |
| @import "foo" print and (-webkit-min-device-pixel-ratio-foo: 25); |
| CSS |
| $media: print; |
| $key: -webkit-min-device-pixel-ratio; |
| $value: 20; |
| @import "foo" \#{$media} and ($key + "-foo": $value + 5); |
| SCSS |
| end |
| |
| def test_http_import |
| assert_equal("@import \"http://fonts.googleapis.com/css?family=Droid+Sans\";\n", |
| render("@import \"http://fonts.googleapis.com/css?family=Droid+Sans\";")) |
| end |
| |
| def test_protocol_relative_import |
| assert_equal("@import \"//fonts.googleapis.com/css?family=Droid+Sans\";\n", |
| render("@import \"//fonts.googleapis.com/css?family=Droid+Sans\";")) |
| end |
| |
| def test_import_with_interpolation |
| assert_equal <<CSS, render(<<SCSS) |
| @import url("http://fonts.googleapis.com/css?family=Droid+Sans"); |
| CSS |
| $family: unquote("Droid+Sans"); |
| @import url("http://fonts.googleapis.com/css?family=\#{$family}"); |
| SCSS |
| end |
| |
| def test_url_import |
| assert_equal("@import url(fonts.sass);\n", render("@import url(fonts.sass);")) |
| end |
| |
| def test_css_import_doesnt_move_through_comments |
| assert_equal <<CSS, render(<<SCSS) |
| /* Comment 1 */ |
| @import url("foo.css"); |
| /* Comment 2 */ |
| @import url("bar.css"); |
| CSS |
| /* Comment 1 */ |
| @import url("foo.css"); |
| |
| /* Comment 2 */ |
| @import url("bar.css"); |
| SCSS |
| end |
| |
| def test_css_import_movement_stops_at_comments |
| assert_equal <<CSS, render(<<SCSS) |
| /* Comment 1 */ |
| @import url("foo.css"); |
| /* Comment 2 */ |
| @import url("bar.css"); |
| .foo { |
| a: b; } |
| |
| /* Comment 3 */ |
| CSS |
| /* Comment 1 */ |
| @import url("foo.css"); |
| |
| /* Comment 2 */ |
| |
| .foo {a: b} |
| |
| /* Comment 3 */ |
| @import url("bar.css"); |
| SCSS |
| end |
| |
| def test_block_comment_in_script |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: 1bar; } |
| CSS |
| foo {a: 1 + /* flang */ bar} |
| SCSS |
| end |
| |
| def test_line_comment_in_script |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: 1blang; } |
| CSS |
| foo {a: 1 + // flang } |
| blang } |
| SCSS |
| end |
| |
| def test_static_hyphenated_unit |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: 0px; } |
| CSS |
| foo {a: 10px-10px } |
| SCSS |
| end |
| |
| ## Nested Rules |
| |
| def test_nested_rules |
| assert_equal <<CSS, render(<<SCSS) |
| foo bar { |
| a: b; } |
| CSS |
| foo {bar {a: b}} |
| SCSS |
| assert_equal <<CSS, render(<<SCSS) |
| foo bar { |
| a: b; } |
| foo baz { |
| b: c; } |
| CSS |
| foo { |
| bar {a: b} |
| baz {b: c}} |
| SCSS |
| assert_equal <<CSS, render(<<SCSS) |
| foo bar baz { |
| a: b; } |
| foo bang bip { |
| a: b; } |
| CSS |
| foo { |
| bar {baz {a: b}} |
| bang {bip {a: b}}} |
| SCSS |
| end |
| |
| def test_nested_rules_with_declarations |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: b; } |
| foo bar { |
| c: d; } |
| CSS |
| foo { |
| a: b; |
| bar {c: d}} |
| SCSS |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: b; } |
| foo bar { |
| c: d; } |
| CSS |
| foo { |
| bar {c: d} |
| a: b} |
| SCSS |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| ump: nump; |
| grump: clump; } |
| foo bar { |
| blat: bang; |
| habit: rabbit; } |
| foo bar baz { |
| a: b; } |
| foo bar bip { |
| c: d; } |
| foo bibble bap { |
| e: f; } |
| CSS |
| foo { |
| ump: nump; |
| grump: clump; |
| bar { |
| blat: bang; |
| habit: rabbit; |
| baz {a: b} |
| bip {c: d}} |
| bibble { |
| bap {e: f}}} |
| SCSS |
| end |
| |
| def test_nested_rules_with_fancy_selectors |
| assert_equal <<CSS, render(<<SCSS) |
| foo .bar { |
| a: b; } |
| foo :baz { |
| c: d; } |
| foo bang:bop { |
| e: f; } |
| foo ::qux { |
| g: h; } |
| foo zap::fblthp { |
| i: j; } |
| CSS |
| foo { |
| .bar {a: b} |
| :baz {c: d} |
| bang:bop {e: f} |
| ::qux {g: h} |
| zap::fblthp {i: j}} |
| SCSS |
| end |
| |
| def test_almost_ambiguous_nested_rules_and_declarations |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| bar: baz bang bop biddle woo look at all these elems; } |
| foo bar:baz:bang:bop:biddle:woo:look:at:all:these:pseudoclasses { |
| a: b; } |
| foo bar:baz bang bop biddle woo look at all these elems { |
| a: b; } |
| CSS |
| foo { |
| bar:baz:bang:bop:biddle:woo:look:at:all:these:pseudoclasses {a: b}; |
| bar:baz bang bop biddle woo look at all these elems {a: b}; |
| bar:baz bang bop biddle woo look at all these elems; } |
| SCSS |
| end |
| |
| def test_newlines_in_selectors |
| assert_equal <<CSS, render(<<SCSS) |
| foo |
| bar { |
| a: b; } |
| CSS |
| foo |
| bar {a: b} |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| foo baz, |
| foo bang, |
| bar baz, |
| bar bang { |
| a: b; } |
| CSS |
| foo, |
| bar { |
| baz, |
| bang {a: b}} |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| foo |
| bar baz |
| bang { |
| a: b; } |
| foo |
| bar bip bop { |
| c: d; } |
| CSS |
| foo |
| bar { |
| baz |
| bang {a: b} |
| |
| bip bop {c: d}} |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| foo bang, foo bip |
| bop, bar |
| baz bang, bar |
| baz bip |
| bop { |
| a: b; } |
| CSS |
| foo, bar |
| baz { |
| bang, bip |
| bop {a: b}} |
| SCSS |
| end |
| |
| def test_trailing_comma_in_selector |
| assert_equal <<CSS, render(<<SCSS) |
| #foo #bar, |
| #baz #boom { |
| a: b; } |
| |
| #bip #bop { |
| c: d; } |
| CSS |
| #foo #bar,, |
| ,#baz #boom, {a: b} |
| |
| #bip #bop, ,, {c: d} |
| SCSS |
| end |
| |
| def test_parent_selectors |
| assert_equal <<CSS, render(<<SCSS) |
| foo:hover { |
| a: b; } |
| bar foo.baz { |
| c: d; } |
| CSS |
| foo { |
| &:hover {a: b} |
| bar &.baz {c: d}} |
| SCSS |
| end |
| |
| def test_parent_selector_with_subject |
| silence_warnings {assert_equal <<CSS, render(<<SCSS)} |
| bar foo.baz! .bip { |
| a: b; } |
| |
| bar foo bar.baz! .bip { |
| c: d; } |
| CSS |
| foo { |
| bar &.baz! .bip {a: b}} |
| |
| foo bar { |
| bar &.baz! .bip {c: d}} |
| SCSS |
| end |
| |
| def test_parent_selector_with_suffix |
| assert_equal <<CSS, render(<<SCSS) |
| .foo-bar { |
| a: b; } |
| .foo_bar { |
| c: d; } |
| .foobar { |
| e: f; } |
| .foo123 { |
| e: f; } |
| |
| :hover-suffix { |
| g: h; } |
| CSS |
| .foo { |
| &-bar {a: b} |
| &_bar {c: d} |
| &bar {e: f} |
| &123 {e: f} |
| } |
| |
| :hover { |
| &-suffix {g: h} |
| } |
| SCSS |
| end |
| |
| def test_unknown_directive_bubbling |
| assert_equal(<<CSS, render(<<SCSS, :style => :nested)) |
| @fblthp { |
| .foo .bar { |
| a: b; } } |
| CSS |
| .foo { |
| @fblthp { |
| .bar {a: b} |
| } |
| } |
| SCSS |
| end |
| |
| def test_keyframe_bubbling |
| assert_equal(<<CSS, render(<<SCSS, :style => :nested)) |
| @keyframes spin { |
| 0% { |
| transform: rotate(0deg); } } |
| @-webkit-keyframes spin { |
| 0% { |
| transform: rotate(0deg); } } |
| CSS |
| .foo { |
| @keyframes spin { |
| 0% {transform: rotate(0deg)} |
| } |
| @-webkit-keyframes spin { |
| 0% {transform: rotate(0deg)} |
| } |
| } |
| SCSS |
| end |
| |
| ## Namespace Properties |
| |
| def test_namespace_properties |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| bar: baz; |
| bang-bip: 1px; |
| bang-bop: bar; } |
| CSS |
| foo { |
| bar: baz; |
| bang: { |
| bip: 1px; |
| bop: bar;}} |
| SCSS |
| end |
| |
| def test_several_namespace_properties |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| bar: baz; |
| bang-bip: 1px; |
| bang-bop: bar; |
| buzz-fram: "foo"; |
| buzz-frum: moo; } |
| CSS |
| foo { |
| bar: baz; |
| bang: { |
| bip: 1px; |
| bop: bar;} |
| buzz: { |
| fram: "foo"; |
| frum: moo; |
| } |
| } |
| SCSS |
| end |
| |
| def test_nested_namespace_properties |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| bar: baz; |
| bang-bip: 1px; |
| bang-bop: bar; |
| bang-blat-baf: bort; } |
| CSS |
| foo { |
| bar: baz; |
| bang: { |
| bip: 1px; |
| bop: bar; |
| blat:{baf:bort}}} |
| SCSS |
| end |
| |
| def test_namespace_properties_with_value |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| bar: baz; |
| bar-bip: bop; |
| bar-bing: bop; } |
| CSS |
| foo { |
| bar: baz { |
| bip: bop; |
| bing: bop; }} |
| SCSS |
| end |
| |
| def test_namespace_properties_with_script_value |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| bar: bazbang; |
| bar-bip: bop; |
| bar-bing: bop; } |
| CSS |
| foo { |
| bar: baz + bang { |
| bip: bop; |
| bing: bop; }} |
| SCSS |
| end |
| |
| def test_no_namespace_properties_without_space |
| assert_equal <<CSS, render(<<SCSS) |
| foo bar:baz { |
| bip: bop; } |
| CSS |
| foo { |
| bar:baz { |
| bip: bop }} |
| SCSS |
| end |
| |
| def test_no_namespace_properties_without_space_even_when_its_unambiguous |
| render(<<SCSS) |
| foo { |
| bar:baz calc(1 + 2) { |
| bip: bop }} |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after "bar:baz calc": expected selector, was "(1 + 2)"', e.message |
| assert_equal 2, e.sass_line |
| end |
| |
| def test_namespace_properties_without_space_allowed_for_non_identifier |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| bar: 1px; |
| bar-bip: bop; } |
| CSS |
| foo { |
| bar:1px { |
| bip: bop }} |
| SCSS |
| end |
| |
| ## Mixins |
| |
| def test_basic_mixins |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: b; } |
| CSS |
| @mixin foo { |
| .foo {a: b}} |
| |
| @include foo; |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| bar { |
| c: d; } |
| bar .foo { |
| a: b; } |
| CSS |
| @mixin foo { |
| .foo {a: b}} |
| |
| bar { |
| @include foo; |
| c: d; } |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| bar { |
| a: b; |
| c: d; } |
| CSS |
| @mixin foo {a: b} |
| |
| bar { |
| @include foo; |
| c: d; } |
| SCSS |
| end |
| |
| def test_mixins_with_empty_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: b; } |
| CSS |
| @mixin foo() {a: b} |
| |
| .foo {@include foo();} |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: b; } |
| CSS |
| @mixin foo() {a: b} |
| |
| .foo {@include foo;} |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: b; } |
| CSS |
| @mixin foo {a: b} |
| |
| .foo {@include foo();} |
| SCSS |
| end |
| |
| def test_mixins_with_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: bar; } |
| CSS |
| @mixin foo($a) {a: $a} |
| |
| .foo {@include foo(bar)} |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: bar; |
| b: 12px; } |
| CSS |
| @mixin foo($a, $b) { |
| a: $a; |
| b: $b; } |
| |
| .foo {@include foo(bar, 12px)} |
| SCSS |
| end |
| |
| def test_keyframes_rules_in_content |
| 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 |
| @mixin keyframes { |
| @keyframes identifier { @content } |
| } |
| |
| @include keyframes { |
| 0% {top: 0; left: 0} |
| \#{"30%"} {top: 50px} |
| 68%, 72% {left: 50px} |
| 100% {top: 100px; left: 100%} |
| } |
| SCSS |
| end |
| |
| ## Functions |
| |
| def test_basic_function |
| assert_equal(<<CSS, render(<<SASS)) |
| bar { |
| a: 3; } |
| CSS |
| @function foo() { |
| @return 1 + 2; |
| } |
| |
| bar { |
| a: foo(); |
| } |
| SASS |
| end |
| |
| def test_function_args |
| assert_equal(<<CSS, render(<<SASS)) |
| bar { |
| a: 3; } |
| CSS |
| @function plus($var1, $var2) { |
| @return $var1 + $var2; |
| } |
| |
| bar { |
| a: plus(1, 2); |
| } |
| SASS |
| end |
| |
| def test_disallowed_function_names |
| Sass::Deprecation.allow_double_warnings do |
| assert_warning(<<WARNING) {render(<<SCSS)} |
| DEPRECATION WARNING on line 1 of test_disallowed_function_names_inline.scss: |
| Naming a function "calc" is disallowed and will be an error in future versions of Sass. |
| This name conflicts with an existing CSS function with special parse rules. |
| WARNING |
| @function calc() {} |
| SCSS |
| |
| assert_warning(<<WARNING) {render(<<SCSS)} |
| DEPRECATION WARNING on line 1 of test_disallowed_function_names_inline.scss: |
| Naming a function "-my-calc" is disallowed and will be an error in future versions of Sass. |
| This name conflicts with an existing CSS function with special parse rules. |
| WARNING |
| @function -my-calc() {} |
| SCSS |
| |
| assert_warning(<<WARNING) {render(<<SCSS)} |
| DEPRECATION WARNING on line 1 of test_disallowed_function_names_inline.scss: |
| Naming a function "element" is disallowed and will be an error in future versions of Sass. |
| This name conflicts with an existing CSS function with special parse rules. |
| WARNING |
| @function element() {} |
| SCSS |
| |
| assert_warning(<<WARNING) {render(<<SCSS)} |
| DEPRECATION WARNING on line 1 of test_disallowed_function_names_inline.scss: |
| Naming a function "-my-element" is disallowed and will be an error in future versions of Sass. |
| This name conflicts with an existing CSS function with special parse rules. |
| WARNING |
| @function -my-element() {} |
| SCSS |
| |
| assert_warning(<<WARNING) {render(<<SCSS)} |
| DEPRECATION WARNING on line 1 of test_disallowed_function_names_inline.scss: |
| Naming a function "expression" is disallowed and will be an error in future versions of Sass. |
| This name conflicts with an existing CSS function with special parse rules. |
| WARNING |
| @function expression() {} |
| SCSS |
| |
| assert_warning(<<WARNING) {render(<<SCSS)} |
| DEPRECATION WARNING on line 1 of test_disallowed_function_names_inline.scss: |
| Naming a function "url" is disallowed and will be an error in future versions of Sass. |
| This name conflicts with an existing CSS function with special parse rules. |
| WARNING |
| @function url() {} |
| SCSS |
| end |
| end |
| |
| def test_allowed_function_names |
| assert_no_warning {assert_equal(<<CSS, render(<<SCSS))} |
| .a { |
| b: c; } |
| CSS |
| @function -my-expression() {@return c} |
| |
| .a {b: -my-expression()} |
| SCSS |
| |
| assert_no_warning {assert_equal(<<CSS, render(<<SCSS))} |
| .a { |
| b: c; } |
| CSS |
| @function -my-url() {@return c} |
| |
| .a {b: -my-url()} |
| SCSS |
| end |
| |
| ## Var Args |
| |
| def test_mixin_var_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2, 3, 4; } |
| CSS |
| @mixin foo($a, $b...) { |
| a: $a; |
| b: $b; |
| } |
| |
| .foo {@include foo(1, 2, 3, 4)} |
| SCSS |
| end |
| |
| def test_mixin_empty_var_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 0; } |
| CSS |
| @mixin foo($a, $b...) { |
| a: $a; |
| b: length($b); |
| } |
| |
| .foo {@include foo(1)} |
| SCSS |
| end |
| |
| def test_mixin_var_args_act_like_list |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 3; |
| b: 3; } |
| CSS |
| @mixin foo($a, $b...) { |
| a: length($b); |
| b: nth($b, 2); |
| } |
| |
| .foo {@include foo(1, 2, 3, 4)} |
| SCSS |
| end |
| |
| def test_mixin_splat_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2; |
| c: 3; |
| d: 4; } |
| CSS |
| @mixin foo($a, $b, $c, $d) { |
| a: $a; |
| b: $b; |
| c: $c; |
| d: $d; |
| } |
| |
| $list: 2, 3, 4; |
| .foo {@include foo(1, $list...)} |
| SCSS |
| end |
| |
| def test_mixin_splat_expression |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2; |
| c: 3; |
| d: 4; } |
| CSS |
| @mixin foo($a, $b, $c, $d) { |
| a: $a; |
| b: $b; |
| c: $c; |
| d: $d; |
| } |
| |
| .foo {@include foo(1, (2, 3, 4)...)} |
| SCSS |
| end |
| |
| def test_mixin_splat_args_with_var_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2, 3, 4; } |
| CSS |
| @mixin foo($a, $b...) { |
| a: $a; |
| b: $b; |
| } |
| |
| $list: 2, 3, 4; |
| .foo {@include foo(1, $list...)} |
| SCSS |
| end |
| |
| def test_mixin_splat_args_with_var_args_and_normal_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2; |
| c: 3, 4; } |
| CSS |
| @mixin foo($a, $b, $c...) { |
| a: $a; |
| b: $b; |
| c: $c; |
| } |
| |
| $list: 2, 3, 4; |
| .foo {@include foo(1, $list...)} |
| SCSS |
| end |
| |
| def test_mixin_splat_args_with_var_args_preserves_separator |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2 3 4 5; } |
| CSS |
| @mixin foo($a, $b...) { |
| a: $a; |
| b: $b; |
| } |
| |
| $list: 3 4 5; |
| .foo {@include foo(1, 2, $list...)} |
| SCSS |
| end |
| |
| def test_mixin_var_and_splat_args_pass_through_keywords |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 3; |
| b: 1; |
| c: 2; } |
| CSS |
| @mixin foo($a...) { |
| @include bar($a...); |
| } |
| |
| @mixin bar($b, $c, $a) { |
| a: $a; |
| b: $b; |
| c: $c; |
| } |
| |
| .foo {@include foo(1, $c: 2, $a: 3)} |
| SCSS |
| end |
| |
| def test_mixin_var_keyword_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2; |
| c: 3; } |
| CSS |
| @mixin foo($args...) { |
| a: map-get(keywords($args), a); |
| b: map-get(keywords($args), b); |
| c: map-get(keywords($args), c); |
| } |
| |
| .foo {@include foo($a: 1, $b: 2, $c: 3)} |
| SCSS |
| end |
| |
| def test_mixin_empty_var_keyword_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| length: 0; } |
| CSS |
| @mixin foo($args...) { |
| length: length(keywords($args)); |
| } |
| |
| .foo {@include foo} |
| SCSS |
| end |
| |
| def test_mixin_map_splat |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2; |
| c: 3; } |
| CSS |
| @mixin foo($a, $b, $c) { |
| a: $a; |
| b: $b; |
| c: $c; |
| } |
| |
| .foo { |
| $map: (a: 1, b: 2, c: 3); |
| @include foo($map...); |
| } |
| SCSS |
| end |
| |
| def test_mixin_map_and_list_splat |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: x; |
| b: y; |
| c: z; |
| d: 1; |
| e: 2; |
| f: 3; } |
| CSS |
| @mixin foo($a, $b, $c, $d, $e, $f) { |
| a: $a; |
| b: $b; |
| c: $c; |
| d: $d; |
| e: $e; |
| f: $f; |
| } |
| |
| .foo { |
| $list: x y z; |
| $map: (d: 1, e: 2, f: 3); |
| @include foo($list..., $map...); |
| } |
| SCSS |
| end |
| |
| def test_mixin_map_splat_takes_precedence_over_pass_through |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2; |
| c: z; } |
| CSS |
| @mixin foo($args...) { |
| $map: (c: z); |
| @include bar($args..., $map...); |
| } |
| |
| @mixin bar($a, $b, $c) { |
| a: $a; |
| b: $b; |
| c: $c; |
| } |
| |
| .foo { |
| @include foo(1, $b: 2, $c: 3); |
| } |
| SCSS |
| end |
| |
| def test_mixin_list_of_pairs_splat_treated_as_list |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: a 1; |
| b: b 2; |
| c: c 3; } |
| CSS |
| @mixin foo($a, $b, $c) { |
| a: $a; |
| b: $b; |
| c: $c; |
| } |
| |
| .foo { |
| @include foo((a 1, b 2, c 3)...); |
| } |
| SCSS |
| end |
| |
| def test_mixin_splat_after_keyword_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2; |
| c: 3; } |
| CSS |
| @mixin foo($a, $b, $c) { |
| a: 1; |
| b: 2; |
| c: 3; |
| } |
| |
| .foo { |
| @include foo(1, $c: 3, 2...); |
| } |
| SCSS |
| end |
| |
| def test_mixin_keyword_args_after_splat |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2; |
| c: 3; } |
| CSS |
| @mixin foo($a, $b, $c) { |
| a: 1; |
| b: 2; |
| c: 3; |
| } |
| |
| .foo { |
| @include foo(1, 2..., $c: 3); |
| } |
| SCSS |
| end |
| |
| def test_mixin_keyword_splat_after_keyword_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2; |
| c: 3; } |
| CSS |
| @mixin foo($a, $b, $c) { |
| a: 1; |
| b: 2; |
| c: 3; |
| } |
| |
| .foo { |
| @include foo(1, $b: 2, (c: 3)...); |
| } |
| SCSS |
| end |
| |
| def test_mixin_triple_keyword_splat_merge |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| foo: 1; |
| bar: 2; |
| kwarg: 3; |
| a: 3; |
| b: 2; |
| c: 3; } |
| CSS |
| @mixin foo($foo, $bar, $kwarg, $a, $b, $c) { |
| foo: $foo; |
| bar: $bar; |
| kwarg: $kwarg; |
| a: $a; |
| b: $b; |
| c: $c; |
| } |
| |
| @mixin bar($args...) { |
| @include foo($args..., $bar: 2, $a: 2, $b: 2, (kwarg: 3, a: 3, c: 3)...); |
| } |
| |
| .foo { |
| @include bar($foo: 1, $a: 1, $b: 1, $c: 1); |
| } |
| SCSS |
| end |
| |
| def test_mixin_map_splat_converts_hyphens_and_underscores_for_real_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: 1; |
| b: 2; |
| c: 3; |
| d: 4; } |
| CSS |
| @mixin foo($a-1, $b-2, $c_3, $d_4) { |
| a: $a-1; |
| b: $b-2; |
| c: $c_3; |
| d: $d_4; |
| } |
| |
| .foo { |
| $map: (a-1: 1, b_2: 2, c-3: 3, d_4: 4); |
| @include foo($map...); |
| } |
| SCSS |
| end |
| |
| def test_mixin_map_splat_doesnt_convert_hyphens_and_underscores_for_var_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a-1: 1; |
| b_2: 2; |
| c-3: 3; |
| d_4: 4; } |
| CSS |
| @mixin foo($args...) { |
| @each $key, $value in keywords($args) { |
| \#{$key}: $value; |
| } |
| } |
| |
| .foo { |
| $map: (a-1: 1, b_2: 2, c-3: 3, d_4: 4); |
| @include foo($map...); |
| } |
| SCSS |
| end |
| |
| def test_mixin_conflicting_splat_after_keyword_args |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)} |
| Mixin foo was passed argument $b both by position and by name. |
| MESSAGE |
| @mixin foo($a, $b, $c) { |
| a: 1; |
| b: 2; |
| c: 3; |
| } |
| |
| .foo { |
| @include foo(1, $b: 2, 3...); |
| } |
| SCSS |
| end |
| |
| def test_mixin_keyword_splat_must_have_string_keys |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS} |
| Variable keyword argument map must have string keys. |
| 12 is not a string in (12: 1). |
| MESSAGE |
| @mixin foo($a) { |
| a: $a; |
| } |
| |
| .foo {@include foo((12: 1)...)} |
| SCSS |
| end |
| |
| def test_mixin_positional_arg_after_splat |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)} |
| Only keyword arguments may follow variable arguments (...). |
| MESSAGE |
| @mixin foo($a, $b, $c) { |
| a: 1; |
| b: 2; |
| c: 3; |
| } |
| |
| .foo { |
| @include foo(1, 2..., 3); |
| } |
| SCSS |
| end |
| |
| def test_mixin_var_args_with_keyword |
| assert_raise_message(Sass::SyntaxError, "Positional arguments must come before keyword arguments.") {render <<SCSS} |
| @mixin foo($a, $b...) { |
| a: $a; |
| b: $b; |
| } |
| |
| .foo {@include foo($a: 1, 2, 3, 4)} |
| SCSS |
| end |
| |
| def test_mixin_keyword_for_var_arg |
| assert_raise_message(Sass::SyntaxError, "Argument $b of mixin foo cannot be used as a named argument.") {render <<SCSS} |
| @mixin foo($a, $b...) { |
| a: $a; |
| b: $b; |
| } |
| |
| .foo {@include foo(1, $b: 2 3 4)} |
| SCSS |
| end |
| |
| def test_mixin_keyword_for_unknown_arg_with_var_args |
| assert_raise_message(Sass::SyntaxError, "Mixin foo doesn't have an argument named $c.") {render <<SCSS} |
| @mixin foo($a, $b...) { |
| a: $a; |
| b: $b; |
| } |
| |
| .foo {@include foo(1, $c: 2 3 4)} |
| SCSS |
| end |
| |
| def test_mixin_map_splat_before_list_splat |
| assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was (2 3)).") {render <<SCSS} |
| @mixin foo($a, $b, $c) { |
| a: $a; |
| b: $b; |
| c: $c; |
| } |
| |
| .foo { |
| @include foo((a: 1)..., (2 3)...); |
| } |
| SCSS |
| end |
| |
| def test_mixin_map_splat_with_unknown_keyword |
| assert_raise_message(Sass::SyntaxError, "Mixin foo doesn't have an argument named $c.") {render <<SCSS} |
| @mixin foo($a, $b) { |
| a: $a; |
| b: $b; |
| } |
| |
| .foo { |
| @include foo(1, 2, (c: 1)...); |
| } |
| SCSS |
| end |
| |
| def test_mixin_map_splat_with_wrong_type |
| assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was 12).") {render <<SCSS} |
| @mixin foo($a, $b) { |
| a: $a; |
| b: $b; |
| } |
| |
| .foo { |
| @include foo((1, 2)..., 12...); |
| } |
| SCSS |
| end |
| |
| def test_mixin_splat_too_many_args |
| assert_warning(<<WARNING) {render <<SCSS} |
| WARNING: Mixin foo takes 2 arguments but 4 were passed. |
| on line 2 of #{filename_for_test(:scss)} |
| This will be an error in future versions of Sass. |
| WARNING |
| @mixin foo($a, $b) {} |
| @include foo((1, 2, 3, 4)...); |
| SCSS |
| end |
| |
| def test_function_var_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2, 3, 4"; } |
| CSS |
| @function foo($a, $b...) { |
| @return "a: \#{$a}, b: \#{$b}"; |
| } |
| |
| .foo {val: foo(1, 2, 3, 4)} |
| SCSS |
| end |
| |
| def test_function_empty_var_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 0"; } |
| CSS |
| @function foo($a, $b...) { |
| @return "a: \#{$a}, b: \#{length($b)}"; |
| } |
| |
| .foo {val: foo(1)} |
| SCSS |
| end |
| |
| def test_function_var_args_act_like_list |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 3, b: 3"; } |
| CSS |
| @function foo($a, $b...) { |
| @return "a: \#{length($b)}, b: \#{nth($b, 2)}"; |
| } |
| |
| .foo {val: foo(1, 2, 3, 4)} |
| SCSS |
| end |
| |
| def test_function_splat_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2, c: 3, d: 4"; } |
| CSS |
| @function foo($a, $b, $c, $d) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}, d: \#{$d}"; |
| } |
| |
| $list: 2, 3, 4; |
| .foo {val: foo(1, $list...)} |
| SCSS |
| end |
| |
| def test_function_splat_expression |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2, c: 3, d: 4"; } |
| CSS |
| @function foo($a, $b, $c, $d) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}, d: \#{$d}"; |
| } |
| |
| .foo {val: foo(1, (2, 3, 4)...)} |
| SCSS |
| end |
| |
| def test_function_splat_args_with_var_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2, 3, 4"; } |
| CSS |
| @function foo($a, $b...) { |
| @return "a: \#{$a}, b: \#{$b}"; |
| } |
| |
| $list: 2, 3, 4; |
| .foo {val: foo(1, $list...)} |
| SCSS |
| end |
| |
| def test_function_splat_args_with_var_args_and_normal_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2, c: 3, 4"; } |
| CSS |
| @function foo($a, $b, $c...) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| $list: 2, 3, 4; |
| .foo {val: foo(1, $list...)} |
| SCSS |
| end |
| |
| def test_function_splat_args_with_var_args_preserves_separator |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2 3 4 5"; } |
| CSS |
| @function foo($a, $b...) { |
| @return "a: \#{$a}, b: \#{$b}"; |
| } |
| |
| $list: 3 4 5; |
| .foo {val: foo(1, 2, $list...)} |
| SCSS |
| end |
| |
| def test_function_var_and_splat_args_pass_through_keywords |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 3, b: 1, c: 2"; } |
| CSS |
| @function foo($a...) { |
| @return bar($a...); |
| } |
| |
| @function bar($b, $c, $a) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| .foo {val: foo(1, $c: 2, $a: 3)} |
| SCSS |
| end |
| |
| def test_function_var_keyword_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2, c: 3"; } |
| CSS |
| @function foo($args...) { |
| @return "a: \#{map-get(keywords($args), a)}, " + |
| "b: \#{map-get(keywords($args), b)}, " + |
| "c: \#{map-get(keywords($args), c)}"; |
| } |
| |
| .foo {val: foo($a: 1, $b: 2, $c: 3)} |
| SCSS |
| end |
| |
| def test_function_empty_var_keyword_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| length: 0; } |
| CSS |
| @function foo($args...) { |
| @return length(keywords($args)); |
| } |
| |
| .foo {length: foo()} |
| SCSS |
| end |
| |
| def test_function_map_splat |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2, c: 3"; } |
| CSS |
| @function foo($a, $b, $c) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| .foo { |
| $map: (a: 1, b: 2, c: 3); |
| val: foo($map...); |
| } |
| SCSS |
| end |
| |
| def test_function_map_and_list_splat |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: x, b: y, c: z, d: 1, e: 2, f: 3"; } |
| CSS |
| @function foo($a, $b, $c, $d, $e, $f) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}, d: \#{$d}, e: \#{$e}, f: \#{$f}"; |
| } |
| |
| .foo { |
| $list: x y z; |
| $map: (d: 1, e: 2, f: 3); |
| val: foo($list..., $map...); |
| } |
| SCSS |
| end |
| |
| def test_function_map_splat_takes_precedence_over_pass_through |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2, c: z"; } |
| CSS |
| @function foo($args...) { |
| $map: (c: z); |
| @return bar($args..., $map...); |
| } |
| |
| @function bar($a, $b, $c) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| .foo { |
| val: foo(1, $b: 2, $c: 3); |
| } |
| SCSS |
| end |
| |
| def test_ruby_function_map_splat_takes_precedence_over_pass_through |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: 1 2 3 z; } |
| CSS |
| @function foo($args...) { |
| $map: (val: z); |
| @return append($args..., $map...); |
| } |
| |
| .foo { |
| val: foo(1 2 3, $val: 4) |
| } |
| SCSS |
| end |
| |
| def test_function_list_of_pairs_splat_treated_as_list |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: a 1, b: b 2, c: c 3"; } |
| CSS |
| @function foo($a, $b, $c) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| .foo { |
| val: foo((a 1, b 2, c 3)...); |
| } |
| SCSS |
| end |
| |
| def test_function_splat_after_keyword_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2, c: 3"; } |
| CSS |
| @function foo($a, $b, $c) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| .foo { |
| val: foo(1, $c: 3, 2...); |
| } |
| SCSS |
| end |
| |
| def test_function_keyword_args_after_splat |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2, c: 3"; } |
| CSS |
| @function foo($a, $b, $c) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| .foo { |
| val: foo(1, 2..., $c: 3); |
| } |
| SCSS |
| end |
| |
| def test_function_keyword_splat_after_keyword_args |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "a: 1, b: 2, c: 3"; } |
| CSS |
| @function foo($a, $b, $c) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| .foo { |
| val: foo(1, $b: 2, (c: 3)...); |
| } |
| SCSS |
| end |
| |
| def test_function_triple_keyword_splat_merge |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: "foo: 1, bar: 2, kwarg: 3, a: 3, b: 2, c: 3"; } |
| CSS |
| @function foo($foo, $bar, $kwarg, $a, $b, $c) { |
| @return "foo: \#{$foo}, bar: \#{$bar}, kwarg: \#{$kwarg}, a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| @function bar($args...) { |
| @return foo($args..., $bar: 2, $a: 2, $b: 2, (kwarg: 3, a: 3, c: 3)...); |
| } |
| |
| .foo { |
| val: bar($foo: 1, $a: 1, $b: 1, $c: 1); |
| } |
| SCSS |
| end |
| |
| def test_function_conflicting_splat_after_keyword_args |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)} |
| Function foo was passed argument $b both by position and by name. |
| MESSAGE |
| @function foo($a, $b, $c) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| .foo { |
| val: foo(1, $b: 2, 3...); |
| } |
| SCSS |
| end |
| |
| def test_function_positional_arg_after_splat |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)} |
| Only keyword arguments may follow variable arguments (...). |
| MESSAGE |
| @function foo($a, $b, $c) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| .foo { |
| val: foo(1, 2..., 3); |
| } |
| SCSS |
| end |
| |
| def test_function_var_args_with_keyword |
| assert_raise_message(Sass::SyntaxError, "Positional arguments must come before keyword arguments.") {render <<SCSS} |
| @function foo($a, $b...) { |
| @return "a: \#{$a}, b: \#{$b}"; |
| } |
| |
| .foo {val: foo($a: 1, 2, 3, 4)} |
| SCSS |
| end |
| |
| def test_function_keyword_for_var_arg |
| assert_raise_message(Sass::SyntaxError, "Argument $b of function foo cannot be used as a named argument.") {render <<SCSS} |
| @function foo($a, $b...) { |
| @return "a: \#{$a}, b: \#{$b}"; |
| } |
| |
| .foo {val: foo(1, $b: 2 3 4)} |
| SCSS |
| end |
| |
| def test_function_keyword_for_unknown_arg_with_var_args |
| assert_raise_message(Sass::SyntaxError, "Function foo doesn't have an argument named $c.") {render <<SCSS} |
| @function foo($a, $b...) { |
| @return "a: \#{$a}, b: \#{length($b)}"; |
| } |
| |
| .foo {val: foo(1, $c: 2 3 4)} |
| SCSS |
| end |
| |
| def test_function_var_args_passed_to_native |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| val: #102035; } |
| CSS |
| @function foo($args...) { |
| @return adjust-color($args...); |
| } |
| |
| .foo {val: foo(#102030, $blue: 5)} |
| SCSS |
| end |
| |
| def test_function_map_splat_before_list_splat |
| assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was (2 3)).") {render <<SCSS} |
| @function foo($a, $b, $c) { |
| @return "a: \#{$a}, b: \#{$b}, c: \#{$c}"; |
| } |
| |
| .foo { |
| val: foo((a: 1)..., (2 3)...); |
| } |
| SCSS |
| end |
| |
| def test_function_map_splat_with_unknown_keyword |
| assert_raise_message(Sass::SyntaxError, "Function foo doesn't have an argument named $c.") {render <<SCSS} |
| @function foo($a, $b) { |
| @return "a: \#{$a}, b: \#{$b}"; |
| } |
| |
| .foo { |
| val: foo(1, 2, (c: 1)...); |
| } |
| SCSS |
| end |
| |
| def test_function_map_splat_with_wrong_type |
| assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was 12).") {render <<SCSS} |
| @function foo($a, $b) { |
| @return "a: \#{$a}, b: \#{$b}"; |
| } |
| |
| .foo { |
| val: foo((1, 2)..., 12...); |
| } |
| SCSS |
| end |
| |
| def test_function_keyword_splat_must_have_string_keys |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS} |
| Variable keyword argument map must have string keys. |
| 12 is not a string in (12: 1). |
| MESSAGE |
| @function foo($a) { |
| @return $a; |
| } |
| |
| .foo {val: foo((12: 1)...)} |
| SCSS |
| end |
| |
| def test_function_splat_too_many_args |
| assert_warning(<<WARNING) {render <<SCSS} |
| WARNING: Function foo takes 2 arguments but 4 were passed. |
| on line 2 of #{filename_for_test(:scss)} |
| This will be an error in future versions of Sass. |
| WARNING |
| @function foo($a, $b) {@return null} |
| $var: foo((1, 2, 3, 4)...); |
| SCSS |
| end |
| |
| ## Interpolation |
| |
| def test_basic_selector_interpolation |
| assert_equal <<CSS, render(<<SCSS) |
| foo ab baz { |
| a: b; } |
| CSS |
| foo \#{'a' + 'b'} baz {a: b} |
| SCSS |
| assert_equal <<CSS, render(<<SCSS) |
| foo.bar baz { |
| a: b; } |
| CSS |
| foo\#{".bar"} baz {a: b} |
| SCSS |
| assert_equal <<CSS, render(<<SCSS) |
| foo.bar baz { |
| a: b; } |
| CSS |
| \#{"foo"}.bar baz {a: b} |
| SCSS |
| end |
| |
| def test_selector_only_interpolation |
| assert_equal <<CSS, render(<<SCSS) |
| foo bar { |
| a: b; } |
| CSS |
| \#{"foo" + " bar"} {a: b} |
| SCSS |
| end |
| |
| def test_selector_interpolation_before_element_name |
| assert_equal <<CSS, render(<<SCSS) |
| foo barbaz { |
| a: b; } |
| CSS |
| \#{"foo" + " bar"}baz {a: b} |
| SCSS |
| end |
| |
| def test_selector_interpolation_in_string |
| assert_equal <<CSS, render(<<SCSS) |
| foo[val="bar foo bar baz"] { |
| a: b; } |
| CSS |
| foo[val="bar \#{"foo" + " bar"} baz"] {a: b} |
| SCSS |
| end |
| |
| def test_selector_interpolation_in_pseudoclass |
| assert_equal <<CSS, render(<<SCSS) |
| foo:nth-child(5n) { |
| a: b; } |
| CSS |
| foo:nth-child(\#{5 + "n"}) {a: b} |
| SCSS |
| end |
| |
| def test_selector_interpolation_at_class_begininng |
| assert_equal <<CSS, render(<<SCSS) |
| .zzz { |
| a: b; } |
| CSS |
| $zzz: zzz; |
| .\#{$zzz} { a: b; } |
| SCSS |
| end |
| |
| def test_selector_interpolation_at_id_begininng |
| assert_equal <<CSS, render(<<SCSS) |
| #zzz { |
| a: b; } |
| CSS |
| $zzz: zzz; |
| #\#{$zzz} { a: b; } |
| SCSS |
| end |
| |
| def test_selector_interpolation_at_pseudo_begininng |
| assert_equal <<CSS, render(<<SCSS) |
| :zzz::zzz { |
| a: b; } |
| CSS |
| $zzz: zzz; |
| :\#{$zzz}::\#{$zzz} { a: b; } |
| SCSS |
| end |
| |
| def test_selector_interpolation_at_attr_beginning |
| assert_equal <<CSS, render(<<SCSS) |
| [zzz=foo] { |
| a: b; } |
| CSS |
| $zzz: zzz; |
| [\#{$zzz}=foo] { a: b; } |
| SCSS |
| end |
| |
| def test_selector_interpolation_at_attr_end |
| assert_equal <<CSS, render(<<SCSS) |
| [foo=zzz] { |
| a: b; } |
| CSS |
| $zzz: zzz; |
| [foo=\#{$zzz}] { a: b; } |
| SCSS |
| end |
| |
| def test_selector_interpolation_at_dashes |
| assert_equal <<CSS, render(<<SCSS) |
| div { |
| -foo-a-b-foo: foo; } |
| CSS |
| $a : a; |
| $b : b; |
| div { -foo-\#{$a}-\#{$b}-foo: foo } |
| SCSS |
| end |
| |
| def test_selector_interpolation_in_reference_combinator |
| silence_warnings {assert_equal <<CSS, render(<<SCSS)} |
| .foo /a/ .bar /b|c/ .baz { |
| a: b; } |
| CSS |
| $a: a; |
| $b: b; |
| $c: c; |
| .foo /\#{$a}/ .bar /\#{$b}|\#{$c}/ .baz {a: b} |
| SCSS |
| end |
| |
| def test_parent_selector_with_parent_and_subject |
| silence_warnings {assert_equal <<CSS, render(<<SCSS)} |
| bar foo.baz! .bip { |
| c: d; } |
| CSS |
| $subject: "!"; |
| foo { |
| bar &.baz\#{$subject} .bip {c: d}} |
| SCSS |
| end |
| |
| def test_basic_prop_name_interpolation |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| barbazbang: blip; } |
| CSS |
| foo {bar\#{"baz" + "bang"}: blip} |
| SCSS |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| bar3: blip; } |
| CSS |
| foo {bar\#{1 + 2}: blip} |
| SCSS |
| end |
| |
| def test_prop_name_only_interpolation |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| bazbang: blip; } |
| CSS |
| foo {\#{"baz" + "bang"}: blip} |
| SCSS |
| end |
| |
| def test_directive_interpolation |
| assert_equal <<CSS, render(<<SCSS) |
| @foo bar12 qux { |
| a: b; } |
| CSS |
| $baz: 12; |
| @foo bar\#{$baz} qux {a: b} |
| SCSS |
| end |
| |
| def test_media_interpolation |
| assert_equal <<CSS, render(<<SCSS) |
| @media bar12 { |
| a: b; } |
| CSS |
| $baz: 12; |
| @media bar\#{$baz} {a: b} |
| SCSS |
| end |
| |
| def test_script_in_media |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen and (-webkit-min-device-pixel-ratio: 20), only print { |
| a: b; } |
| CSS |
| $media1: screen; |
| $media2: print; |
| $var: -webkit-min-device-pixel-ratio; |
| $val: 20; |
| @media \#{$media1} and ($var: $val), only \#{$media2} {a: b} |
| SCSS |
| |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen and (-webkit-min-device-pixel-ratio: 13) { |
| a: b; } |
| CSS |
| $vals: 1 2 3; |
| @media screen and (-webkit-min-device-pixel-ratio: 5 + 6 + nth($vals, 2)) {a: b} |
| SCSS |
| end |
| |
| def test_media_interpolation_with_reparse |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen and (max-width: 300px) { |
| a: b; } |
| @media screen and (max-width: 300px) { |
| a: b; } |
| @media screen and (max-width: 300px) { |
| a: b; } |
| @media screen and (max-width: 300px), print and (max-width: 300px) { |
| a: b; } |
| CSS |
| $constraint: "(max-width: 300px)"; |
| $fragment: "nd \#{$constraint}"; |
| $comma: "een, pri"; |
| @media screen and \#{$constraint} {a: b} |
| @media screen { |
| @media \#{$constraint} {a: b} |
| } |
| @media screen a\#{$fragment} {a: b} |
| @media scr\#{$comma}nt { |
| @media \#{$constraint} {a: b} |
| } |
| SCSS |
| end |
| |
| def test_moz_document_interpolation |
| assert_equal <<CSS, render(<<SCSS) |
| @-moz-document url(http://sass-lang.com/), |
| url-prefix(http://sass-lang.com/docs), |
| domain(sass-lang.com), |
| domain("sass-lang.com") { |
| .foo { |
| a: b; } } |
| CSS |
| $domain: "sass-lang.com"; |
| @-moz-document url(http://\#{$domain}/), |
| url-prefix(http://\#{$domain}/docs), |
| domain(\#{$domain}), |
| \#{domain($domain)} { |
| .foo {a: b} |
| } |
| SCSS |
| end |
| |
| def test_supports_with_expressions |
| assert_equal <<CSS, render(<<SCSS) |
| @supports ((feature1: val) and (feature2: val)) or (not (feature23: val4)) { |
| foo { |
| a: b; } } |
| CSS |
| $query: "(feature1: val)"; |
| $feature: feature2; |
| $val: val; |
| @supports (\#{$query} and ($feature: $val)) or (not ($feature + 3: $val + 4)) { |
| foo {a: b} |
| } |
| SCSS |
| end |
| |
| def test_supports_bubbling |
| assert_equal <<CSS, render(<<SCSS) |
| @supports (foo: bar) { |
| a { |
| b: c; } |
| @supports (baz: bang) { |
| a { |
| d: e; } } } |
| CSS |
| a { |
| @supports (foo: bar) { |
| b: c; |
| @supports (baz: bang) { |
| d: e; |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_random_directive_interpolation |
| assert_equal <<CSS, render(<<SCSS) |
| @foo url(http://sass-lang.com/), |
| domain("sass-lang.com"), |
| "foobarbaz", |
| foobarbaz { |
| .foo { |
| a: b; } } |
| CSS |
| $domain: "sass-lang.com"; |
| @foo url(http://\#{$domain}/), |
| \#{domain($domain)}, |
| "foo\#{'ba' + 'r'}baz", |
| foo\#{'ba' + 'r'}baz { |
| .foo {a: b} |
| } |
| SCSS |
| end |
| |
| def test_color_interpolation_warning_in_selector |
| assert_warning(<<WARNING) {assert_equal <<CSS, render(<<SCSS)} |
| WARNING on line 1, column 4 of #{filename_for_test(:scss)}: |
| You probably don't mean to use the color value `blue' in interpolation here. |
| It may end up represented as #0000ff, which will likely produce invalid CSS. |
| Always quote color names when using them as strings (for example, "blue"). |
| If you really want to use the color value here, use `"" + blue'. |
| WARNING |
| fooblue { |
| a: b; } |
| CSS |
| foo\#{blue} {a: b} |
| SCSS |
| end |
| |
| def test_color_interpolation_warning_in_directive |
| assert_warning(<<WARNING) {assert_equal <<CSS, render(<<SCSS)} |
| WARNING on line 1, column 12 of #{filename_for_test(:scss)}: |
| You probably don't mean to use the color value `blue' in interpolation here. |
| It may end up represented as #0000ff, which will likely produce invalid CSS. |
| Always quote color names when using them as strings (for example, "blue"). |
| If you really want to use the color value here, use `"" + blue'. |
| WARNING |
| @fblthp fooblue { |
| a: b; } |
| CSS |
| @fblthp foo\#{blue} {a: b} |
| SCSS |
| end |
| |
| def test_color_interpolation_warning_in_property_name |
| assert_warning(<<WARNING) {assert_equal <<CSS, render(<<SCSS)} |
| WARNING on line 1, column 8 of #{filename_for_test(:scss)}: |
| You probably don't mean to use the color value `blue' in interpolation here. |
| It may end up represented as #0000ff, which will likely produce invalid CSS. |
| Always quote color names when using them as strings (for example, "blue"). |
| If you really want to use the color value here, use `"" + blue'. |
| WARNING |
| foo { |
| a-blue: b; } |
| CSS |
| foo {a-\#{blue}: b} |
| SCSS |
| end |
| |
| def test_no_color_interpolation_warning_in_property_value |
| assert_no_warning {assert_equal <<CSS, render(<<SCSS)} |
| foo { |
| a: b-blue; } |
| CSS |
| foo {a: b-\#{blue}} |
| SCSS |
| end |
| |
| def test_no_color_interpolation_warning_for_nameless_color |
| assert_no_warning {assert_equal <<CSS, render(<<SCSS)} |
| foo-#abcdef { |
| a: b; } |
| CSS |
| foo-\#{#abcdef} {a: b} |
| SCSS |
| end |
| |
| def test_nested_mixin_def |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: b; } |
| CSS |
| foo { |
| @mixin bar {a: b} |
| @include bar; } |
| SCSS |
| end |
| |
| def test_nested_mixin_shadow |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| c: d; } |
| |
| baz { |
| a: b; } |
| CSS |
| @mixin bar {a: b} |
| |
| foo { |
| @mixin bar {c: d} |
| @include bar; |
| } |
| |
| baz {@include bar} |
| SCSS |
| end |
| |
| def test_nested_function_def |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: 1; } |
| |
| bar { |
| b: foo(); } |
| CSS |
| foo { |
| @function foo() {@return 1} |
| a: foo(); } |
| |
| bar {b: foo()} |
| SCSS |
| end |
| |
| def test_nested_function_shadow |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: 2; } |
| |
| baz { |
| b: 1; } |
| CSS |
| @function foo() {@return 1} |
| |
| foo { |
| @function foo() {@return 2} |
| a: foo(); |
| } |
| |
| baz {b: foo()} |
| SCSS |
| end |
| |
| ## @at-root |
| |
| def test_simple_at_root |
| assert_equal <<CSS, render(<<SCSS) |
| .bar { |
| a: b; } |
| CSS |
| .foo { |
| @at-root { |
| .bar {a: b} |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_with_selector |
| assert_equal <<CSS, render(<<SCSS) |
| .bar { |
| a: b; } |
| CSS |
| .foo { |
| @at-root .bar {a: b} |
| } |
| SCSS |
| end |
| |
| def test_at_root_in_mixin |
| assert_equal <<CSS, render(<<SCSS) |
| .bar { |
| a: b; } |
| CSS |
| @mixin bar { |
| @at-root .bar {a: b} |
| } |
| |
| .foo { |
| @include bar; |
| } |
| SCSS |
| end |
| |
| def test_at_root_in_media |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| .bar { |
| a: b; } } |
| CSS |
| @media screen { |
| .foo { |
| @at-root .bar {a: b} |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_in_bubbled_media |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| .bar { |
| a: b; } } |
| CSS |
| .foo { |
| @media screen { |
| @at-root .bar {a: b} |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_in_unknown_directive |
| assert_equal <<CSS, render(<<SCSS) |
| @fblthp { |
| .bar { |
| a: b; } } |
| CSS |
| @fblthp { |
| .foo { |
| @at-root .bar {a: b} |
| } |
| } |
| SCSS |
| end |
| |
| def test_comments_in_at_root |
| assert_equal <<CSS, render(<<SCSS) |
| /* foo */ |
| .bar { |
| a: b; } |
| |
| /* baz */ |
| CSS |
| .foo { |
| @at-root { |
| /* foo */ |
| .bar {a: b} |
| /* baz */ |
| } |
| } |
| SCSS |
| end |
| |
| def test_comments_in_at_root_in_media |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| /* foo */ |
| .bar { |
| a: b; } |
| |
| /* baz */ } |
| CSS |
| @media screen { |
| .foo { |
| @at-root { |
| /* foo */ |
| .bar {a: b} |
| /* baz */ |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_comments_in_at_root_in_unknown_directive |
| assert_equal <<CSS, render(<<SCSS) |
| @fblthp { |
| /* foo */ |
| .bar { |
| a: b; } |
| |
| /* baz */ } |
| CSS |
| @fblthp { |
| .foo { |
| @at-root { |
| /* foo */ |
| .bar {a: b} |
| /* baz */ |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_media_directive_in_at_root |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| .bar { |
| a: b; } } |
| CSS |
| .foo { |
| @at-root { |
| @media screen {.bar {a: b}} |
| } |
| } |
| SCSS |
| end |
| |
| def test_bubbled_media_directive_in_at_root |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| .bar .baz { |
| a: b; } } |
| CSS |
| .foo { |
| @at-root { |
| .bar { |
| @media screen {.baz {a: b}} |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_unknown_directive_in_at_root |
| assert_equal <<CSS, render(<<SCSS) |
| @fblthp { |
| .bar { |
| a: b; } } |
| CSS |
| .foo { |
| @at-root { |
| @fblthp {.bar {a: b}} |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_in_at_root |
| assert_equal <<CSS, render(<<SCSS) |
| .bar { |
| a: b; } |
| CSS |
| .foo { |
| @at-root { |
| @at-root .bar {a: b} |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_with_parent_ref |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: b; } |
| CSS |
| .foo { |
| @at-root & { |
| a: b; |
| } |
| } |
| SCSS |
| end |
| |
| def test_multi_level_at_root_with_parent_ref |
| assert_equal <<CSS, render(<<SCSS) |
| .foo .bar { |
| a: b; } |
| CSS |
| .foo { |
| @at-root & { |
| .bar { |
| @at-root & { |
| a: b; |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_multi_level_at_root_with_inner_parent_ref |
| assert_equal <<CSS, render(<<SCSS) |
| .bar { |
| a: b; } |
| CSS |
| .foo { |
| @at-root .bar { |
| @at-root & { |
| a: b; |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_beneath_comma_selector |
| assert_equal(<<CSS, render(<<SCSS)) |
| .baz { |
| a: b; } |
| CSS |
| .foo, .bar { |
| @at-root .baz { |
| a: b; |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_with_parent_ref_and_class |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo.bar { |
| a: b; } |
| CSS |
| .foo { |
| @at-root &.bar { |
| a: b; |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_beneath_comma_selector_with_parent_ref |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo.baz, .bar.baz { |
| a: b; } |
| CSS |
| .foo, .bar { |
| @at-root &.baz { |
| a: b; |
| } |
| } |
| SCSS |
| end |
| |
| ## @at-root (...) |
| |
| def test_at_root_without_media |
| assert_equal <<CSS, render(<<SCSS) |
| .foo .bar { |
| a: b; } |
| CSS |
| .foo { |
| @media screen { |
| @at-root (without: media) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_without_supports |
| assert_equal <<CSS, render(<<SCSS) |
| .foo .bar { |
| a: b; } |
| CSS |
| .foo { |
| @supports (foo: bar) { |
| @at-root (without: supports) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_without_rule |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| .bar { |
| a: b; } } |
| CSS |
| .foo { |
| @media screen { |
| @at-root (without: rule) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_without_unknown_directive |
| assert_equal <<CSS, render(<<SCSS) |
| @fblthp {} |
| .foo .bar { |
| a: b; } |
| CSS |
| .foo { |
| @fblthp { |
| @at-root (without: fblthp) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_without_multiple |
| assert_equal <<CSS, render(<<SCSS) |
| @supports (foo: bar) { |
| .bar { |
| a: b; } } |
| CSS |
| .foo { |
| @media screen { |
| @supports (foo: bar) { |
| @at-root (without: media rule) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_without_all |
| assert_equal <<CSS, render(<<SCSS) |
| @supports (foo: bar) { |
| @fblthp {} } |
| .bar { |
| a: b; } |
| CSS |
| .foo { |
| @supports (foo: bar) { |
| @fblthp { |
| @at-root (without: all) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_with_media |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| @fblthp {} |
| .bar { |
| a: b; } } |
| CSS |
| .foo { |
| @media screen { |
| @fblthp { |
| @supports (foo: bar) { |
| @at-root (with: media) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_with_rule |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| @fblthp {} } |
| .foo .bar { |
| a: b; } |
| CSS |
| .foo { |
| @media screen { |
| @fblthp { |
| @supports (foo: bar) { |
| @at-root (with: rule) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_with_supports |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| @fblthp {} } |
| @supports (foo: bar) { |
| .bar { |
| a: b; } } |
| CSS |
| .foo { |
| @media screen { |
| @fblthp { |
| @supports (foo: bar) { |
| @at-root (with: supports) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_with_unknown_directive |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| @fblthp {} } |
| @fblthp { |
| .bar { |
| a: b; } } |
| CSS |
| .foo { |
| @media screen { |
| @fblthp { |
| @supports (foo: bar) { |
| @at-root (with: fblthp) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_with_multiple |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| @fblthp {} |
| .foo .bar { |
| a: b; } } |
| CSS |
| .foo { |
| @media screen { |
| @fblthp { |
| @supports (foo: bar) { |
| @at-root (with: media rule) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_with_all |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| @fblthp { |
| @supports (foo: bar) { |
| .foo .bar { |
| a: b; } } } } |
| CSS |
| .foo { |
| @media screen { |
| @fblthp { |
| @supports (foo: bar) { |
| @at-root (with: all) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_dynamic_values |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| .bar { |
| a: b; } } |
| CSS |
| $key: with; |
| $value: media; |
| .foo { |
| @media screen { |
| @at-root ($key: $value) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_interpolated_query |
| assert_equal <<CSS, render(<<SCSS) |
| @media screen { |
| .bar { |
| a: b; } } |
| CSS |
| .foo { |
| @media screen { |
| @at-root (\#{"with: media"}) { |
| .bar { |
| a: b; |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_plus_extend |
| assert_equal <<CSS, render(<<SCSS) |
| .foo .bar { |
| a: b; } |
| CSS |
| %base { |
| a: b; |
| } |
| |
| .foo { |
| @media screen { |
| @at-root (without: media) { |
| .bar { |
| @extend %base; |
| } |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_without_keyframes_in_keyframe_rule |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: b; } |
| CSS |
| @keyframes identifier { |
| 0% { |
| @at-root (without: keyframes) { |
| .foo {a: b} |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_without_rule_in_keyframe_rule |
| assert_equal <<CSS, render(<<SCSS) |
| @keyframes identifier { |
| 0% { |
| a: b; } } |
| CSS |
| @keyframes identifier { |
| 0% { |
| @at-root (without: rule) {a: b} |
| } |
| } |
| SCSS |
| end |
| |
| ## Selector Script |
| |
| def test_selector_script |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo .bar { |
| content: ".foo .bar"; } |
| CSS |
| .foo .bar { |
| content: "\#{&}"; |
| } |
| SCSS |
| end |
| |
| def test_nested_selector_script |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo .bar { |
| content: ".foo .bar"; } |
| CSS |
| .foo { |
| .bar { |
| content: "\#{&}"; |
| } |
| } |
| SCSS |
| end |
| |
| def test_nested_selector_script_with_outer_comma_selector |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo .baz, .bar .baz { |
| content: ".foo .baz, .bar .baz"; } |
| CSS |
| .foo, .bar { |
| .baz { |
| content: "\#{&}"; |
| } |
| } |
| SCSS |
| end |
| |
| def test_nested_selector_script_with_inner_comma_selector |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo .bar, .foo .baz { |
| content: ".foo .bar, .foo .baz"; } |
| CSS |
| .foo { |
| .bar, .baz { |
| content: "\#{&}"; |
| } |
| } |
| SCSS |
| end |
| |
| def test_selector_script_through_mixin |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo { |
| content: ".foo"; } |
| CSS |
| @mixin mixin { |
| content: "\#{&}"; |
| } |
| |
| .foo { |
| @include mixin; |
| } |
| SCSS |
| end |
| |
| def test_selector_script_through_content |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo { |
| content: ".foo"; } |
| CSS |
| @mixin mixin { |
| @content; |
| } |
| |
| .foo { |
| @include mixin { |
| content: "\#{&}"; |
| } |
| } |
| SCSS |
| end |
| |
| def test_selector_script_through_function |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo { |
| content: ".foo"; } |
| CSS |
| @function fn() { |
| @return "\#{&}"; |
| } |
| |
| .foo { |
| content: fn(); |
| } |
| SCSS |
| end |
| |
| def test_selector_script_through_media |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo { |
| content: "outer"; } |
| @media screen { |
| .foo .bar { |
| content: ".foo .bar"; } } |
| CSS |
| .foo { |
| content: "outer"; |
| @media screen { |
| .bar { |
| content: "\#{&}"; |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_selector_script_save_and_reuse |
| assert_equal(<<CSS, render(<<SCSS)) |
| .bar { |
| content: ".foo"; } |
| CSS |
| $var: null; |
| .foo { |
| $var: & !global; |
| } |
| |
| .bar { |
| content: "\#{$var}"; |
| } |
| SCSS |
| end |
| |
| def test_selector_script_with_at_root |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo-bar { |
| a: b; } |
| CSS |
| .foo { |
| @at-root \#{&}-bar { |
| a: b; |
| } |
| } |
| SCSS |
| end |
| |
| def test_multi_level_at_root_with_inner_selector_script |
| assert_equal <<CSS, render(<<SCSS) |
| .bar { |
| a: b; } |
| CSS |
| .foo { |
| @at-root .bar { |
| @at-root \#{&} { |
| a: b; |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_at_root_with_at_root_through_mixin |
| assert_equal(<<CSS, render(<<SCSS)) |
| .bar-baz { |
| a: b; } |
| CSS |
| @mixin foo { |
| .bar { |
| @at-root \#{&}-baz { |
| a: b; |
| } |
| } |
| } |
| |
| @include foo; |
| SCSS |
| end |
| |
| # See https://github.com/sass/sass/issues/1294 |
| def test_extend_top_leveled_by_at_root |
| render(<<SCSS) |
| .span-10 { |
| @at-root (without: all) { |
| @extend %column; |
| } |
| } |
| SCSS |
| |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal "Extend directives may only be used within rules.", e.message |
| assert_equal 3, e.sass_line |
| end |
| |
| def test_at_root_doesnt_always_break_blocks |
| assert_equal <<CSS, render(<<SCSS) |
| .foo { |
| a: b; } |
| |
| @media screen { |
| .foo { |
| c: d; } |
| .bar { |
| e: f; } } |
| CSS |
| %base { |
| a: b; |
| } |
| |
| @media screen { |
| .foo { |
| c: d; |
| @at-root (without: media) { |
| @extend %base; |
| } |
| } |
| |
| .bar {e: f} |
| } |
| SCSS |
| end |
| |
| ## Errors |
| |
| def test_keyframes_rule_outside_of_keyframes |
| render <<SCSS |
| 0% { |
| top: 0; } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after "": expected selector, was "0%"', e.message |
| assert_equal 1, e.sass_line |
| end |
| |
| def test_selector_rule_in_keyframes |
| render <<SCSS |
| @keyframes identifier { |
| .foo { |
| top: 0; } } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after "": expected keyframes selector (e.g. 10%), was ".foo"', e.message |
| assert_equal 2, e.sass_line |
| end |
| |
| def test_nested_mixin_def_is_scoped |
| render <<SCSS |
| foo { |
| @mixin bar {a: b}} |
| bar {@include bar} |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal "Undefined mixin 'bar'.", e.message |
| assert_equal 3, e.sass_line |
| end |
| |
| def test_rules_beneath_properties |
| render <<SCSS |
| foo { |
| bar: { |
| baz { |
| bang: bop }}} |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Illegal nesting: Only properties may be nested beneath properties.', e.message |
| assert_equal 3, e.sass_line |
| end |
| |
| def test_uses_property_exception_with_star_hack |
| render <<SCSS |
| foo { |
| *bar:baz <fail>; } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after " *bar:baz <fail>": expected expression (e.g. 1px, bold), was "; }"', e.message |
| assert_equal 2, e.sass_line |
| end |
| |
| def test_uses_property_exception_with_colon_hack |
| render <<SCSS |
| foo { |
| :bar:baz <fail>; } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after " :bar:baz <fail>": expected expression (e.g. 1px, bold), was "; }"', e.message |
| assert_equal 2, e.sass_line |
| end |
| |
| def test_uses_rule_exception_with_dot_hack |
| render <<SCSS |
| foo { |
| .bar:baz <fail>; } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after " .bar:baz <fail>": expected expression (e.g. 1px, bold), was "; }"', e.message |
| assert_equal 2, e.sass_line |
| end |
| |
| def test_uses_property_exception_with_space_after_name |
| render <<SCSS |
| foo { |
| bar: baz <fail>; } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after " bar: baz <fail>": expected expression (e.g. 1px, bold), was "; }"', e.message |
| assert_equal 2, e.sass_line |
| end |
| |
| def test_uses_property_exception_with_non_identifier_after_name |
| render <<SCSS |
| foo { |
| bar:1px <fail>; } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after " bar:1px <fail>": expected expression (e.g. 1px, bold), was "; }"', e.message |
| assert_equal 2, e.sass_line |
| end |
| |
| def test_uses_property_exception_when_followed_by_open_bracket |
| render <<SCSS |
| foo { |
| bar:{baz: .fail} } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after " bar:{baz: ": expected expression (e.g. 1px, bold), was ".fail} }"', e.message |
| assert_equal 2, e.sass_line |
| end |
| |
| def test_script_error |
| render <<SCSS |
| foo { |
| bar: "baz" * * } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after " bar: "baz" * ": expected expression (e.g. 1px, bold), was "* }"', e.message |
| assert_equal 2, e.sass_line |
| end |
| |
| def test_multiline_script_syntax_error |
| render <<SCSS |
| foo { |
| bar: |
| "baz" * * } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after " "baz" * ": expected expression (e.g. 1px, bold), was "* }"', e.message |
| assert_equal 3, e.sass_line |
| end |
| |
| def test_multiline_script_runtime_error |
| render <<SCSS |
| foo { |
| bar: "baz" + |
| "bar" + |
| $bang } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal "Undefined variable: \"$bang\".", e.message |
| assert_equal 4, e.sass_line |
| end |
| |
| def test_post_multiline_script_runtime_error |
| render <<SCSS |
| foo { |
| bar: "baz" + |
| "bar" + |
| "baz"; |
| bip: $bop; } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal "Undefined variable: \"$bop\".", e.message |
| assert_equal 5, e.sass_line |
| end |
| |
| def test_multiline_property_runtime_error |
| render <<SCSS |
| foo { |
| bar: baz |
| bar |
| \#{$bang} } |
| SCSS |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal "Undefined variable: \"$bang\".", e.message |
| assert_equal 4, e.sass_line |
| end |
| |
| def test_post_resolution_selector_error |
| render "\n\nfoo \#{\") bar\"} {a: b}" |
| assert(false, "Expected syntax error") |
| rescue Sass::SyntaxError => e |
| assert_equal 'Invalid CSS after "foo ": expected selector, was ") bar"', e.message |
| assert_equal 3, e.sass_line |
| end |
| |
| def test_parent_in_mid_selector_error |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS} |
| Invalid CSS after ".foo": expected "{", was "&.bar" |
| |
| "&.bar" may only be used at the beginning of a compound selector. |
| MESSAGE |
| flim { |
| .foo&.bar {a: b} |
| } |
| SCSS |
| end |
| |
| def test_parent_after_selector_error |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS} |
| Invalid CSS after ".foo.bar": expected "{", was "&" |
| |
| "&" may only be used at the beginning of a compound selector. |
| MESSAGE |
| flim { |
| .foo.bar& {a: b} |
| } |
| SCSS |
| end |
| |
| def test_double_parent_selector_error |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS} |
| Invalid CSS after "&": expected "{", was "&" |
| |
| "&" may only be used at the beginning of a compound selector. |
| MESSAGE |
| flim { |
| && {a: b} |
| } |
| SCSS |
| end |
| |
| def test_no_lonely_else |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS} |
| Invalid CSS: @else must come after @if |
| MESSAGE |
| @else {foo: bar} |
| SCSS |
| end |
| |
| def test_failed_parent_selector_with_suffix |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)} |
| Invalid parent selector for "&-bar": "*" |
| MESSAGE |
| * { |
| &-bar {a: b} |
| } |
| SCSS |
| |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)} |
| Invalid parent selector for "&-bar": "[foo=bar]" |
| MESSAGE |
| [foo=bar] { |
| &-bar {a: b} |
| } |
| SCSS |
| |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)} |
| Invalid parent selector for "&-bar": "::nth-child(2n+1)" |
| MESSAGE |
| ::nth-child(2n+1) { |
| &-bar {a: b} |
| } |
| SCSS |
| |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)} |
| Invalid parent selector for "&-bar": ":not(.foo)" |
| MESSAGE |
| :not(.foo) { |
| &-bar {a: b} |
| } |
| SCSS |
| |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)} |
| Invalid parent selector for "&-bar": ".foo +" |
| MESSAGE |
| .foo + { |
| &-bar {a: b} |
| } |
| SCSS |
| end |
| |
| def test_empty_media_query_error |
| assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)} |
| Invalid CSS after "": expected media query list, was "" |
| MESSAGE |
| @media \#{""} { |
| foo {a: b} |
| } |
| SCSS |
| end |
| |
| def test_newline_in_property_value |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo { |
| bar: "bazbang"; } |
| CSS |
| .foo { |
| $var: "baz\\ |
| bang"; |
| bar: $var; |
| } |
| SCSS |
| end |
| |
| def test_raw_newline_warning |
| assert_warning(<<MESSAGE.rstrip) {assert_equal(<<CSS, render(<<SCSS))} |
| DEPRECATION WARNING on line 2, column 9 of #{filename_for_test :scss}: |
| Unescaped multiline strings are deprecated and will be removed in a future version of Sass. |
| To include a newline in a string, use "\\a" or "\\a " as in CSS. |
| MESSAGE |
| .foo { |
| bar: "baz\\a bang"; } |
| CSS |
| .foo { |
| $var: "baz |
| bang"; |
| bar: $var; |
| } |
| SCSS |
| end |
| |
| # Regression |
| |
| # Regression test for #2031. |
| def test_no_interpolation_warning_in_nested_selector |
| assert_no_warning {assert_equal(<<CSS, render(<<SCSS))} |
| z a:b(n+1) { |
| x: y; } |
| CSS |
| z { |
| a:b(n+\#{1}) { |
| x: y; |
| } |
| } |
| SCSS |
| end |
| |
| # Ensures that the fix for #2031 doesn't hide legitimate warnings. |
| def test_interpolation_warning_in_selector_like_property |
| assert_warning(<<WARNING) {assert_equal(<<CSS, render(<<SCSS))} |
| DEPRECATION WARNING on line 2 of #{filename_for_test :scss}: |
| \#{} interpolation near operators will be simplified in a future version of Sass. |
| To preserve the current behavior, use quotes: |
| |
| unquote("n+1") |
| |
| You can use the sass-convert command to automatically fix most cases. |
| WARNING |
| z { |
| a: b(n+1); } |
| CSS |
| z { |
| a:b(n+\#{1}); |
| } |
| SCSS |
| end |
| |
| def test_escape_in_selector |
| assert_equal(<<CSS, render(".\\!foo {a: b}")) |
| .\\!foo { |
| a: b; } |
| CSS |
| end |
| |
| def test_for_directive_with_float_bounds |
| assert_equal(<<CSS, render(<<SCSS)) |
| .a { |
| b: 0; |
| b: 1; |
| b: 2; |
| b: 3; |
| b: 4; |
| b: 5; } |
| CSS |
| .a { |
| @for $i from 0.0 through 5.0 {b: $i} |
| } |
| SCSS |
| |
| assert_raise_message(Sass::SyntaxError, "0.5 is not an integer.") {render(<<SCSS)} |
| .a { |
| @for $i from 0.5 through 5.0 {b: $i} |
| } |
| SCSS |
| |
| assert_raise_message(Sass::SyntaxError, "5.5 is not an integer.") {render(<<SCSS)} |
| .a { |
| @for $i from 0.0 through 5.5 {b: $i} |
| } |
| SCSS |
| end |
| |
| def test_parent_selector_in_function_pseudo_selector |
| assert_equal <<CSS, render(<<SCSS) |
| .bar:not(.foo) { |
| a: b; } |
| |
| .qux:nth-child(2n of .baz .bang) { |
| c: d; } |
| CSS |
| .foo { |
| .bar:not(&) {a: b} |
| } |
| |
| .baz .bang { |
| .qux:nth-child(2n of &) {c: d} |
| } |
| SCSS |
| end |
| |
| def test_parent_selector_in_and_out_of_function_pseudo_selector |
| # Regression test for https://github.com/sass/sass/issues/1464#issuecomment-70352288 |
| assert_equal(<<CSS, render(<<SCSS)) |
| .a:not(.a-b) { |
| x: y; } |
| CSS |
| .a { |
| &:not(&-b) { |
| x: y; |
| } |
| } |
| SCSS |
| |
| assert_equal(<<CSS, render(<<SCSS)) |
| .a:nth-child(2n of .a-b) { |
| x: y; } |
| CSS |
| .a { |
| &:nth-child(2n of &-b) { |
| x: y; |
| } |
| } |
| SCSS |
| end |
| |
| def test_attribute_selector_in_selector_pseudoclass |
| # Even though this is plain CSS, it only failed when given to the SCSS |
| # parser. |
| assert_equal(<<CSS, render(<<SCSS)) |
| [href^='http://'] { |
| color: red; } |
| CSS |
| [href^='http://'] { |
| color: red; |
| } |
| SCSS |
| end |
| |
| def test_top_level_unknown_directive_in_at_root |
| assert_equal(<<CSS, render(<<SCSS)) |
| @fblthp { |
| a: b; } |
| CSS |
| @at-root { |
| @fblthp {a: b} |
| } |
| SCSS |
| end |
| |
| def test_parent_ref_with_newline |
| assert_equal(<<CSS, render(<<SCSS)) |
| a.c |
| , b.c { |
| x: y; } |
| CSS |
| a |
| , b {&.c {x: y}} |
| SCSS |
| end |
| |
| def test_parent_ref_in_nested_at_root |
| assert_equal(<<CSS, render(<<SCSS)) |
| #test { |
| border: 0; } |
| #test:hover { |
| display: none; } |
| CSS |
| a { |
| @at-root #test { |
| border: 0; |
| &:hover{ |
| display: none; |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_loud_comment_in_compressed_mode |
| assert_equal(<<CSS, render(<<SCSS)) |
| /*! foo */ |
| CSS |
| /*! foo */ |
| SCSS |
| end |
| |
| def test_parsing_decimals_followed_by_comments_doesnt_take_forever |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo { |
| padding: 4.2105263158% 4.2105263158% 5.6315789474%; } |
| CSS |
| .foo { |
| padding: 4.21052631578947% 4.21052631578947% 5.631578947368421% /**/ |
| } |
| SCSS |
| end |
| |
| def test_parsing_many_numbers_doesnt_take_forever |
| values = ["80% 90%"] * 1000 |
| assert_equal(<<CSS, render(<<SCSS)) |
| .foo { |
| padding: #{values.join(', ')}; } |
| CSS |
| .foo { |
| padding: #{values.join(', ')}; |
| } |
| SCSS |
| end |
| |
| def test_import_comments_in_imports |
| assert_equal(<<CSS, render(<<SCSS)) |
| @import url(foo.css); |
| @import url(bar.css); |
| @import url(baz.css); |
| CSS |
| @import "foo.css", // this is a comment |
| "bar.css", /* this is another comment */ |
| "baz.css"; // this is a third comment |
| SCSS |
| end |
| |
| def test_reference_combinator_with_parent_ref |
| silence_warnings {assert_equal <<CSS, render(<<SCSS)} |
| a /foo/ b { |
| c: d; } |
| CSS |
| a {& /foo/ b {c: d}} |
| SCSS |
| end |
| |
| def test_reference_combinator_warning |
| assert_warning(<<WARNING) {assert_equal <<CSS, render(<<SCSS)} |
| DEPRECATION WARNING on line 1, column 8 of test_reference_combinator_warning_inline.scss: |
| The reference combinator /foo/ is deprecated and will be removed in a future release. |
| WARNING |
| a /foo/ b { |
| c: d; } |
| CSS |
| a {& /foo/ b {c: d}} |
| SCSS |
| end |
| |
| def test_newline_selector_rendered_multiple_times |
| assert_equal <<CSS, render(<<SCSS) |
| form input, |
| form select { |
| color: white; } |
| |
| form input, |
| form select { |
| color: white; } |
| CSS |
| @for $i from 1 through 2 { |
| form { |
| input, |
| select { |
| color: white; |
| } |
| } |
| } |
| SCSS |
| end |
| |
| def test_prop_name_interpolation_after_hyphen |
| assert_equal <<CSS, render(<<SCSS) |
| a { |
| -foo-bar: b; } |
| CSS |
| a { -\#{"foo"}-bar: b; } |
| SCSS |
| end |
| |
| def test_star_plus_and_parent |
| assert_equal <<CSS, render(<<SCSS) |
| * + html foo { |
| a: b; } |
| CSS |
| foo {*+html & {a: b}} |
| SCSS |
| end |
| |
| def test_weird_added_space |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| bar: -moz-bip; } |
| CSS |
| $value : bip; |
| |
| foo { |
| bar: -moz-\#{$value}; |
| } |
| SCSS |
| end |
| |
| def test_interpolation_with_bracket_on_next_line |
| assert_equal <<CSS, render(<<SCSS) |
| a.foo b { |
| color: red; } |
| CSS |
| a.\#{"foo"} b |
| {color: red} |
| SCSS |
| end |
| |
| def test_extra_comma_in_mixin_arglist |
| assert_equal <<CSS, render(<<SCSS) |
| .bar { |
| baz: bar; } |
| CSS |
| @mixin foo($a1,) { |
| baz: $a1; |
| } |
| |
| .bar { |
| @include foo(bar, ); |
| } |
| SCSS |
| end |
| |
| |
| def test_extra_comma_between_parameters_in_mixin_arglist |
| assert_raise_message(Sass::SyntaxError, "Invalid CSS after \"...nclude foo(bar,\": expected \")\", was \", baz );\"") {render <<SCSS} |
| @mixin foo($a1, $a2) { |
| baz: $a1; |
| bef: $a2; |
| } |
| |
| .bar { |
| @include foo(bar,, baz ); |
| } |
| SCSS |
| end |
| |
| |
| def test_extra_comma_in_mixin_arglist_ending_needs_have_parentheses_after |
| assert_raise_message(Sass::SyntaxError, "Invalid CSS after \" bri,\": expected \")\", was \"};\"") {render <<SCSS} |
| @mixin foo($a1, $a2) { |
| baz: $a1; |
| bal: $a2; |
| } |
| |
| .bar { |
| @include foo( |
| bar, |
| bri, |
| }; |
| } |
| SCSS |
| end |
| |
| |
| |
| def test_interpolation |
| assert_equal <<CSS, render(<<SCSS) |
| ul li#foo a span.label { |
| foo: bar; } |
| CSS |
| $bar : "#foo"; |
| ul li\#{$bar} a span.label { foo: bar; } |
| SCSS |
| end |
| |
| def test_mixin_with_keyword_args |
| assert_equal <<CSS, render(<<SCSS) |
| .mixed { |
| required: foo; |
| arg1: default-val1; |
| arg2: non-default-val2; } |
| CSS |
| @mixin a-mixin($required, $arg1: default-val1, $arg2: default-val2) { |
| required: $required; |
| arg1: $arg1; |
| arg2: $arg2; |
| } |
| .mixed { @include a-mixin(foo, $arg2: non-default-val2); } |
| SCSS |
| end |
| |
| def test_keyword_arg_scope |
| assert_equal <<CSS, render(<<SCSS) |
| .mixed { |
| arg1: default; |
| arg2: non-default; } |
| CSS |
| $arg1: default; |
| $arg2: default; |
| @mixin a-mixin($arg1: $arg1, $arg2: $arg2) { |
| arg1: $arg1; |
| arg2: $arg2; |
| } |
| .mixed { @include a-mixin($arg2: non-default); } |
| SCSS |
| end |
| |
| def test_passing_required_args_as_a_keyword_arg |
| assert_equal <<CSS, render(<<SCSS) |
| .mixed { |
| required: foo; |
| arg1: default-val1; |
| arg2: default-val2; } |
| CSS |
| @mixin a-mixin($required, $arg1: default-val1, $arg2: default-val2) { |
| required: $required; |
| arg1: $arg1; |
| arg2: $arg2; } |
| .mixed { @include a-mixin($required: foo); } |
| SCSS |
| end |
| |
| def test_passing_all_as_keyword_args_in_opposite_order |
| assert_equal <<CSS, render(<<SCSS) |
| .mixed { |
| required: foo; |
| arg1: non-default-val1; |
| arg2: non-default-val2; } |
| CSS |
| @mixin a-mixin($required, $arg1: default-val1, $arg2: default-val2) { |
| required: $required; |
| arg1: $arg1; |
| arg2: $arg2; } |
| .mixed { @include a-mixin($arg2: non-default-val2, $arg1: non-default-val1, $required: foo); } |
| SCSS |
| end |
| |
| def test_keyword_args_in_functions |
| assert_equal <<CSS, render(<<SCSS) |
| .keyed { |
| color: rgba(170, 119, 204, 0.4); } |
| CSS |
| .keyed { color: rgba($color: #a7c, $alpha: 0.4) } |
| SCSS |
| end |
| |
| def test_unknown_keyword_arg_raises_error |
| assert_raise_message(Sass::SyntaxError, "Mixin a doesn't have an argument named $c.") {render <<SCSS} |
| @mixin a($b: 1) { a: $b; } |
| div { @include a(1, $c: 3); } |
| SCSS |
| end |
| |
| |
| def test_newlines_removed_from_selectors_when_compressed |
| assert_equal <<CSS, render(<<SCSS, :style => :compressed) |
| z a,z b{display:block} |
| CSS |
| a |
| , b { |
| z & { |
| display: block; |
| } |
| } |
| SCSS |
| end |
| |
| def test_if_error_line |
| assert_raise_line(2) {render(<<SCSS)} |
| @if true {foo: bar} |
| } |
| SCSS |
| end |
| |
| def test_multiline_var |
| assert_equal <<CSS, render(<<SCSS) |
| foo { |
| a: 3; |
| b: false; |
| c: a b c; } |
| CSS |
| foo { |
| $var1: 1 + |
| 2; |
| $var2: true and |
| false; |
| $var3: a b |
| c; |
| a: $var1; |
| b: $var2; |
| c: $var3; } |
| SCSS |
| end |
| |
| def test_mixin_content |
| assert_equal <<CSS, render(<<SASS) |
| .parent { |
| background-color: red; |
| border-color: red; } |
| .parent .child { |
| background-color: yellow; |
| color: blue; |
| border-color: yellow; } |
| CSS |
| $color: blue; |
| @mixin context($class, $color: red) { |
| .\#{$class} { |
| background-color: $color; |
| @content; |
| border-color: $color; |
| } |
| } |
| @include context(parent) { |
| @include context(child, $color: yellow) { |
| color: $color; |
| } |
| } |
| SASS |
| end |
| |
| def test_empty_content |
| assert_equal <<CSS, render(<<SCSS) |
| a { |
| b: c; } |
| CSS |
| @mixin foo { @content } |
| a { b: c; @include foo {} } |
| SCSS |
| end |
| |
| def test_options_passed_to_script |
| assert_equal <<CSS, render(<<SCSS, :style => :compressed) |
| foo{color:#000} |
| CSS |
| foo {color: darken(black, 10%)} |
| SCSS |
| end |
| |
| # ref: https://github.com/nex3/sass/issues/104 |
| def test_no_buffer_overflow |
| template = render <<SCSS |
| .aaa { |
| background-color: white; |
| } |
| .aaa .aaa .aaa { |
| background-color: black; |
| } |
| .bbb { |
| @extend .aaa; |
| } |
| .xxx { |
| @extend .bbb; |
| } |
| .yyy { |
| @extend .bbb; |
| } |
| .zzz { |
| @extend .bbb; |
| } |
| SCSS |
| Sass::SCSS::Parser.new(template, "test.scss", nil).parse |
| end |
| |
| def test_extend_in_media_in_rule |
| assert_equal(<<CSS, render(<<SCSS)) |
| @media screen { |
| .foo { |
| a: b; } } |
| CSS |
| .foo { |
| @media screen { |
| @extend %bar; |
| } |
| } |
| |
| @media screen { |
| %bar { |
| a: b; |
| } |
| } |
| SCSS |
| end |
| |
| def test_import_with_supports_clause_interp |
| assert_equal(<<CSS, render(<<'SASS', :style => :compressed)) |
| @import url("fallback-layout.css") supports(not (display: flex)) |
| CSS |
| $display-type: flex; |
| @import url("fallback-layout.css") supports(not (display: #{$display-type})); |
| SASS |
| end |
| |
| def test_import_with_supports_clause |
| assert_equal(<<CSS, render(<<SASS, :style => :compressed)) |
| @import url("fallback-layout.css") supports(not (display: flex));.foo{bar:baz} |
| CSS |
| @import url("fallback-layout.css") supports(not (display: flex)); |
| .foo { bar: baz; } |
| SASS |
| end |
| |
| def test_crlf |
| # Attempt to reproduce https://github.com/sass/sass/issues/1985 |
| assert_equal(<<CSS, render(<<SCSS)) |
| p { |
| margin: 0; } |
| CSS |
| p {\r\n margin: 0;\r\n} |
| SCSS |
| end |
| |
| end |