#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require File.dirname(__FILE__) + '/test_helper'

class ScssTest < Test::Unit::TestCase
  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_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_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;
  }
}
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_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_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_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

  ## 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; }
CSS
foo {
  .bar {a: b}
  :baz {c: d}
  bang:bop {e: f}}
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
    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

  ## 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:1px {
    bip: bop }}
SCSS
    assert(false, "Expected syntax error")
  rescue Sass::SyntaxError => e
    assert_equal <<MESSAGE, e.message
Invalid CSS: a space is required between a property and its definition
when it has other properties nested beneath it.
MESSAGE
    assert_equal 2, e.sass_line
  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

  ## 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

  ## 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_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_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_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: \#{$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

  ## Interpolation

  def test_basic_selector_interpolation
    assert_equal <<CSS, render(<<SCSS)
foo 3 baz {
  a: b; }
CSS
foo \#{1 + 2} 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
    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
    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_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

  ## Errors

  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 ": expected ";", was "[fail]; }"', 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 ": expected ";", was "[fail]; }"', 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 ": expected "{", was "<fail>; }"', 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 ": expected ";", was "[fail]; }"', 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 ": expected ";", was "[fail]; }"', 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 {a: b}"

"&.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 "& {a: b}"

"&" 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 "& {a: b}"

"&" 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

  # Regression

  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.21053% 4.21053% 5.63158%; }
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
    assert_equal <<CSS, render(<<SCSS)
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_error
    assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS}
Invalid CSS after "...clude foo(bar, ": expected mixin argument, was ");"
MESSAGE
@mixin foo($a1, $a2) {
  baz: $a1 $a2;
}

.bar {
  @include foo(bar, );
}
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_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").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
end
