sass 3.1.0.alpha.15 → 3.1.0.alpha.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,13 @@ module Sass::Script
32
32
  # @param options [{Symbol => Object}] The options
33
33
  def options=(options)
34
34
  @options = options
35
- children.each {|c| c.options = options}
35
+ children.each do |c|
36
+ if c.is_a? Hash
37
+ c.values.each {|v| v.options = options }
38
+ else
39
+ c.options = options
40
+ end
41
+ end
36
42
  end
37
43
 
38
44
  # Sets the context for this node,
@@ -74,19 +74,21 @@ module Sass
74
74
 
75
75
  # Parses the argument list for a mixin include.
76
76
  #
77
- # @return [Array<Script::Node>] The root nodes of the arguments.
77
+ # @return [(Array<Script::Node>, {String => Script::Note})]
78
+ # The root nodes of the arguments.
79
+ # Keyword arguments are in a hash from names to values.
78
80
  # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
79
81
  def parse_mixin_include_arglist
80
- args = []
81
-
82
+ args, keywords = [], {}
82
83
  if try_tok(:lparen)
83
- args = arglist || args
84
+ args, keywords = mixin_arglist || [[], {}]
84
85
  assert_tok(:rparen)
85
86
  end
86
87
  assert_done
87
88
 
88
89
  args.each {|a| a.options = @options}
89
- args
90
+ keywords.each {|k, v| v.options = @options}
91
+ return args, keywords
90
92
  rescue Sass::SyntaxError => e
91
93
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
92
94
  raise e
@@ -254,9 +256,9 @@ RUBY
254
256
 
255
257
  def funcall
256
258
  return raw unless tok = try_tok(:funcall)
257
- args = fn_arglist || []
259
+ args, keywords = fn_arglist || [[], {}]
258
260
  assert_tok(:rparen)
259
- node(Script::Funcall.new(tok.value, args))
261
+ node(Script::Funcall.new(tok.value, args, keywords))
260
262
  end
261
263
 
262
264
  def defn_arglist!(must_have_default)
@@ -289,15 +291,48 @@ RUBY
289
291
  end
290
292
 
291
293
  def fn_arglist
292
- return unless e = equals
293
- return [e] unless try_tok(:comma)
294
- [e, *assert_expr(:fn_arglist)]
294
+ arglist(:fn_arglist, :equals)
295
+ end
296
+
297
+ def mixin_arglist
298
+ arglist(:mixin_arglist, :interpolation)
299
+ end
300
+
301
+ def arglist(type, subexpr)
302
+ return unless e = send(subexpr)
303
+ if @lexer.peek && @lexer.peek.type == :colon
304
+ name = e
305
+ @lexer.expected!("comma") unless name.is_a?(Variable)
306
+ assert_tok(:colon)
307
+ keywords = {name.underscored_name => assert_expr(subexpr, EXPR_NAMES[type])}
308
+ end
309
+
310
+ unless try_tok(:comma)
311
+ return [], keywords if keywords
312
+ return [e], {}
313
+ end
314
+
315
+ other_args, other_keywords = assert_expr(type)
316
+ if keywords
317
+ if other_keywords[name.underscored_name]
318
+ raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
319
+ end
320
+ return other_args, keywords.merge(other_keywords)
321
+ else
322
+ return [e, *other_args], other_keywords
323
+ end
295
324
  end
296
325
 
297
- def arglist
298
- return unless e = interpolation
299
- return [e] unless try_tok(:comma)
300
- [e, *assert_expr(:arglist)]
326
+ def keyword_arglist
327
+ return unless var = try_tok(:const)
328
+ unless try_tok(:colon)
329
+ return_tok!
330
+ return
331
+ end
332
+ name = var[1]
333
+ value = interpolation
334
+ return {name => value} unless try_tok(:comma)
335
+ {name => value}.merge(assert_expr(:keyword_arglist))
301
336
  end
302
337
 
303
338
  def raw
@@ -359,13 +394,13 @@ RUBY
359
394
  EXPR_NAMES = {
360
395
  :string => "string",
361
396
  :default => "expression (e.g. 1px, bold)",
362
- :arglist => "mixin argument",
397
+ :mixin_arglist => "mixin argument",
363
398
  :fn_arglist => "function argument",
364
399
  }
365
400
 
366
- def assert_expr(name)
401
+ def assert_expr(name, expected = nil)
367
402
  (e = send(name)) && (return e)
368
- @lexer.expected!(EXPR_NAMES[name] || EXPR_NAMES[:default])
403
+ @lexer.expected!(expected || EXPR_NAMES[name] || EXPR_NAMES[:default])
369
404
  end
370
405
 
371
406
  def assert_tok(*names)
@@ -7,9 +7,15 @@ module Sass
7
7
  # @return [String]
8
8
  attr_reader :name
9
9
 
10
+ # The underscored name of the variable.
11
+ #
12
+ # @return [String]
13
+ attr_reader :underscored_name
14
+
10
15
  # @param name [String] See \{#name}
11
16
  def initialize(name)
12
17
  @name = name
18
+ @underscored_name = name.gsub(/-/,"_")
13
19
  super()
14
20
  end
15
21
 
@@ -138,9 +138,9 @@ module Sass
138
138
 
139
139
  def include_directive
140
140
  name = tok! IDENT
141
- args = sass_script(:parse_mixin_include_arglist)
141
+ args, keywords = sass_script(:parse_mixin_include_arglist)
142
142
  ss
143
- node(Sass::Tree::MixinNode.new(name, args))
143
+ node(Sass::Tree::MixinNode.new(name, args, keywords))
144
144
  end
145
145
 
146
146
  def debug_directive
@@ -11,13 +11,16 @@ module Sass::Tree
11
11
  def options=(opts)
12
12
  super
13
13
  @args.each {|a| a.context = :equals} if opts[:sass2]
14
+ @keywords.each {|k, v| v.context = :equals} if opts[:sass2]
14
15
  end
15
16
 
16
17
  # @param name [String] The name of the mixin
17
18
  # @param args [Array<Script::Node>] The arguments to the mixin
18
- def initialize(name, args)
19
+ # @param keywords [{String => Script::Node}] A hash from keyword argument names to values
20
+ def initialize(name, args, keywords)
19
21
  @name = name
20
22
  @args = args
23
+ @keywords = keywords
21
24
  super()
22
25
  end
23
26
 
@@ -42,8 +45,12 @@ module Sass::Tree
42
45
 
43
46
  # @see Node#to_src
44
47
  def to_src(tabs, opts, fmt)
45
- args = '(' + @args.map {|a| a.to_sass(opts)}.join(", ") + ')' unless @args.empty?
46
- "#{' ' * tabs}#{fmt == :sass ? '+' : '@include '}#{dasherize(@name, opts)}#{args}#{semi fmt}\n"
48
+ unless @args.empty? && @keywords.empty?
49
+ args = @args.map {|a| a.to_sass(opts)}.join(", ")
50
+ keywords = @keywords.map {|k, v| "$#{dasherize(k, opts)}: #{v.to_sass(opts)}"}.join(', ')
51
+ arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
52
+ end
53
+ "#{' ' * tabs}#{fmt == :sass ? '+' : '@include '}#{dasherize(@name, opts)}#{arglist}#{semi fmt}\n"
47
54
  end
48
55
 
49
56
  # @see Node#_cssize
@@ -73,15 +80,28 @@ module Sass::Tree
73
80
  original_env.prepare_frame(:mixin => @name)
74
81
  raise Sass::SyntaxError.new("Undefined mixin '#{@name}'.") unless mixin = environment.mixin(@name)
75
82
 
76
- raise Sass::SyntaxError.new(<<END.gsub("\n", "")) if mixin.args.size < @args.size
83
+ passed_args = @args.dup
84
+ passed_keywords = @keywords.dup
85
+
86
+ raise Sass::SyntaxError.new(<<END.gsub("\n", "")) if mixin.args.size < passed_args.size
77
87
  Mixin #{@name} takes #{mixin.args.size} argument#{'s' if mixin.args.size != 1}
78
88
  but #{@args.size} #{@args.size == 1 ? 'was' : 'were'} passed.
79
89
  END
80
- environment = mixin.args.zip(@args).
90
+
91
+ passed_keywords.each do |name, value|
92
+ # TODO: Make this fast
93
+ unless mixin.args.find {|(var, default)| var.underscored_name == name}
94
+ raise Sass::SyntaxError.new("Mixin #{@name} doesn't have an argument named $#{name}")
95
+ end
96
+ end
97
+
98
+ environment = mixin.args.zip(passed_args).
81
99
  inject(Sass::Environment.new(mixin.environment)) do |env, ((var, default), value)|
82
100
  env.set_local_var(var.name,
83
101
  if value
84
102
  value.perform(environment)
103
+ elsif kv = passed_keywords[var.underscored_name]
104
+ kv.perform(env)
85
105
  elsif default
86
106
  val = default.perform(env)
87
107
  if default.context == :equals && val.is_a?(Sass::Script::String)
@@ -859,6 +859,20 @@ foo {
859
859
  SCSS
860
860
  end
861
861
 
862
+ def test_mixin_include_with_keyword_args
863
+ assert_renders <<SASS, <<SCSS
864
+ foo
865
+ +foo-bar(12px, "blaz", $blip: blap, $bloop: blop)
866
+ +foo-bar($blip: blap, $bloop: blop)
867
+ a: blip
868
+ SASS
869
+ foo {
870
+ @include foo-bar(12px, "blaz", $blip: blap, $bloop: blop);
871
+ @include foo-bar($blip: blap, $bloop: blop);
872
+ a: blip; }
873
+ SCSS
874
+ end
875
+
862
876
  def test_variable_definition
863
877
  assert_renders <<SASS, <<SCSS
864
878
  $var1: 12px + 15px
@@ -94,6 +94,7 @@ MSG
94
94
  "a-\#{$b\n c: d" => ['Invalid CSS after "a-#{$b": expected "}", was ""', 1],
95
95
  "=a($b = 1, $c)" => "Required argument $c must come before any optional arguments.",
96
96
  "=a($b = 1)\n a: $b\ndiv\n +a(1,2)" => "Mixin a takes 1 argument but 2 were passed.",
97
+ "=a($b: 1)\n a: $b\ndiv\n +a(1,$c: 3)" => "Mixin a doesn't have an argument named $c",
97
98
  "=a($b)\n a: $b\ndiv\n +a" => "Mixin a is missing parameter $b.",
98
99
  "@else\n a\n b: c" => ["@else must come after @if.", 1],
99
100
  "@if false\n@else foo" => "Invalid else directive '@else foo': expected 'if <expr>'.",
@@ -115,6 +116,16 @@ MSG
115
116
  '@warn' => "Invalid warn directive '@warn': expected expression.",
116
117
  %Q{@warn "a message"\n "nested message"} => "Illegal nesting: Nothing may be nested beneath warn directives.",
117
118
  "/* foo\n bar\n baz" => "Inconsistent indentation: previous line was indented by 4 spaces, but this line was indented by 2 spaces.",
119
+ '+foo(1 + 1: 2)' => 'Invalid CSS after "(1 + 1": expected comma, was ": 2)"',
120
+ '+foo($var: )' => 'Invalid CSS after "($var: ": expected mixin argument, was ")"',
121
+ '+foo($var: a, $var: b)' => 'Keyword argument "$var" passed more than once',
122
+ '+foo($var-var: a, $var_var: b)' => 'Keyword argument "$var-var" passed more than once',
123
+ '+foo($var_var: a, $var-var: b)' => 'Keyword argument "$var_var" passed more than once',
124
+ "a\n b: foo(1 + 1: 2)" => 'Invalid CSS after "foo(1 + 1": expected comma, was ": 2)"',
125
+ "a\n b: foo($var: )" => 'Invalid CSS after "foo($var: ": expected function argument, was ")"',
126
+ "a\n b: foo($var: a, $var: b)" => 'Keyword argument "$var" passed more than once',
127
+ "a\n b: foo($var-var: a, $var_var: b)" => 'Keyword argument "$var-var" passed more than once',
128
+ "a\n b: foo($var_var: a, $var-var: b)" => 'Keyword argument "$var_var" passed more than once',
118
129
 
119
130
  # Regression tests
120
131
  "a\n b:\n c\n d" => ["Illegal nesting: Only properties may be nested beneath properties.", 3],
@@ -2101,6 +2112,70 @@ CSS
2101
2112
  SASS
2102
2113
  end
2103
2114
 
2115
+ def test_mixin_with_keyword_args
2116
+ assert_equal <<CSS, render(<<SASS)
2117
+ .mixed {
2118
+ required: foo;
2119
+ arg1: default-val1;
2120
+ arg2: non-default-val2; }
2121
+ CSS
2122
+ =a-mixin($required, $arg1: default-val1, $arg2: default-val2)
2123
+ required: $required
2124
+ arg1: $arg1
2125
+ arg2: $arg2
2126
+ .mixed
2127
+ +a-mixin(foo, $arg2: non-default-val2)
2128
+ SASS
2129
+ end
2130
+
2131
+ def test_mixin_keyword_args_handle_variable_underscore_dash_equivalence
2132
+ assert_equal <<CSS, render(<<SASS)
2133
+ .mixed {
2134
+ required: foo;
2135
+ arg1: non-default-val1;
2136
+ arg2: non-default-val2; }
2137
+ CSS
2138
+ =a-mixin($required, $arg-1: default-val1, $arg_2: default-val2)
2139
+ required: $required
2140
+ arg1: $arg_1
2141
+ arg2: $arg-2
2142
+ .mixed
2143
+ +a-mixin(foo, $arg-2: non-default-val2, $arg_1: non-default-val1)
2144
+ SASS
2145
+ end
2146
+
2147
+ def test_passing_required_args_as_a_keyword_arg
2148
+ assert_equal <<CSS, render(<<SASS)
2149
+ .mixed {
2150
+ required: foo;
2151
+ arg1: default-val1;
2152
+ arg2: default-val2; }
2153
+ CSS
2154
+ =a-mixin($required, $arg1: default-val1, $arg2: default-val2)
2155
+ required: $required
2156
+ arg1: $arg1
2157
+ arg2: $arg2
2158
+ .mixed
2159
+ +a-mixin($required: foo)
2160
+ SASS
2161
+ end
2162
+
2163
+ def test_passing_all_as_keyword_args_in_opposite_order
2164
+ assert_equal <<CSS, render(<<SASS)
2165
+ .mixed {
2166
+ required: foo;
2167
+ arg1: non-default-val1;
2168
+ arg2: non-default-val2; }
2169
+ CSS
2170
+ =a-mixin($required, $arg1: default-val1, $arg2: default-val2)
2171
+ required: $required
2172
+ arg1: $arg1
2173
+ arg2: $arg2
2174
+ .mixed
2175
+ +a-mixin($arg2: non-default-val2, $arg1: non-default-val1, $required: foo)
2176
+ SASS
2177
+ end
2178
+
2104
2179
  def test_function_output_with_comma
2105
2180
  assert_equal <<CSS, render(<<SASS)
2106
2181
  foo {
@@ -3,6 +3,22 @@ require 'test/unit'
3
3
  require File.dirname(__FILE__) + '/../test_helper'
4
4
  require 'sass/script'
5
5
 
6
+ module Sass::Script::Functions
7
+ def no_kw_args
8
+ Sass::Script::String.new("no-kw-args")
9
+ end
10
+
11
+ def only_var_args(*args)
12
+ Sass::Script::String.new("only-var-args("+args.map{|a| a.plus(Sass::Script::Number.new(1)).to_s }.join(", ")+")")
13
+ end
14
+ declare :only_var_args, [], :var_args => true
15
+
16
+ def only_kw_args(kwargs)
17
+ Sass::Script::String.new("only-kw-args("+kwargs.keys.join(", ")+")")
18
+ end
19
+ declare :only_kw_args, [], :var_kwargs => true
20
+ end
21
+
6
22
  module Sass::Script::Functions::UserFunctions
7
23
  def call_options_on_new_literal
8
24
  str = Sass::Script::String.new("foo")
@@ -50,6 +66,10 @@ class SassFunctionTest < Test::Unit::TestCase
50
66
  end
51
67
  end
52
68
 
69
+ def test_hsl_kwargs
70
+ assert_equal "#33cccc", evaluate("hsl($hue: 180, $saturation: 60%, $lightness: 50%)")
71
+ end
72
+
53
73
  def test_hsl_checks_bounds
54
74
  assert_error_message("Saturation -114 must be between 0% and 100% for `hsl'", "hsl(10, -114, 12)");
55
75
  assert_error_message("Lightness 256 must be between 0% and 100% for `hsl'", "hsl(10, 10, 256%)");
@@ -65,6 +85,7 @@ class SassFunctionTest < Test::Unit::TestCase
65
85
  assert_equal "rgba(51, 204, 204, 0.4)", evaluate("hsla(180, 60%, 50%, 0.4)")
66
86
  assert_equal "#33cccc", evaluate("hsla(180, 60%, 50%, 1)")
67
87
  assert_equal "rgba(51, 204, 204, 0)", evaluate("hsla(180, 60%, 50%, 0)")
88
+ assert_equal "rgba(51, 204, 204, 0.4)", evaluate("hsla($hue: 180, $saturation: 60%, $lightness: 50%, $alpha: 0.4)")
68
89
  end
69
90
 
70
91
  def test_hsla_checks_bounds
@@ -85,6 +106,7 @@ class SassFunctionTest < Test::Unit::TestCase
85
106
  assert_equal("50%", evaluate("percentage(.5)"))
86
107
  assert_equal("100%", evaluate("percentage(1)"))
87
108
  assert_equal("25%", evaluate("percentage(25px / 100px)"))
109
+ assert_equal("50%", evaluate("percentage($value: 0.5)"))
88
110
  end
89
111
 
90
112
  def test_percentage_checks_types
@@ -97,6 +119,7 @@ class SassFunctionTest < Test::Unit::TestCase
97
119
  assert_equal("5", evaluate("round(4.8)"))
98
120
  assert_equal("5px", evaluate("round(4.8px)"))
99
121
  assert_equal("5px", evaluate("round(5.49px)"))
122
+ assert_equal("5px", evaluate("round($value: 5.49px)"))
100
123
 
101
124
  assert_error_message("#cccccc is not a number for `round'", "round(#ccc)")
102
125
  end
@@ -104,6 +127,7 @@ class SassFunctionTest < Test::Unit::TestCase
104
127
  def test_floor
105
128
  assert_equal("4", evaluate("floor(4.8)"))
106
129
  assert_equal("4px", evaluate("floor(4.8px)"))
130
+ assert_equal("4px", evaluate("floor($value: 4.8px)"))
107
131
 
108
132
  assert_error_message("\"foo\" is not a number for `floor'", "floor(\"foo\")")
109
133
  end
@@ -111,6 +135,7 @@ class SassFunctionTest < Test::Unit::TestCase
111
135
  def test_ceil
112
136
  assert_equal("5", evaluate("ceil(4.1)"))
113
137
  assert_equal("5px", evaluate("ceil(4.8px)"))
138
+ assert_equal("5px", evaluate("ceil($value: 4.8px)"))
114
139
 
115
140
  assert_error_message("\"a\" is not a number for `ceil'", "ceil(\"a\")")
116
141
  end
@@ -120,6 +145,7 @@ class SassFunctionTest < Test::Unit::TestCase
120
145
  assert_equal("5px", evaluate("abs(-5px)"))
121
146
  assert_equal("5", evaluate("abs(5)"))
122
147
  assert_equal("5px", evaluate("abs(5px)"))
148
+ assert_equal("5px", evaluate("abs($value: 5px)"))
123
149
 
124
150
  assert_error_message("#aaaaaa is not a number for `abs'", "abs(#aaa)")
125
151
  end
@@ -128,6 +154,7 @@ class SassFunctionTest < Test::Unit::TestCase
128
154
  assert_equal("#123456", evaluate("rgb(18, 52, 86)"))
129
155
  assert_equal("#beaded", evaluate("rgb(190, 173, 237)"))
130
156
  assert_equal("#00ff7f", evaluate("rgb(0, 255, 127)"))
157
+ assert_equal("#00ff7f", evaluate("rgb($red: 0, $green: 255, $blue: 127)"))
131
158
  end
132
159
 
133
160
  def test_rgb_percent
@@ -169,6 +196,7 @@ class SassFunctionTest < Test::Unit::TestCase
169
196
  assert_equal("rgba(18, 52, 86, 0.5)", evaluate("rgba(18, 52, 86, 0.5)"))
170
197
  assert_equal("#beaded", evaluate("rgba(190, 173, 237, 1)"))
171
198
  assert_equal("rgba(0, 255, 127, 0)", evaluate("rgba(0, 255, 127, 0)"))
199
+ assert_equal("rgba(0, 255, 127, 0)", evaluate("rgba($red: 0, $green: 255, $blue: 127, $alpha: 0)"))
172
200
  end
173
201
 
174
202
  def test_rgb_tests_bounds
@@ -198,6 +226,7 @@ class SassFunctionTest < Test::Unit::TestCase
198
226
  def test_rgba_with_color
199
227
  assert_equal "rgba(16, 32, 48, 0.5)", evaluate("rgba(#102030, 0.5)")
200
228
  assert_equal "rgba(0, 0, 255, 0.5)", evaluate("rgba(blue, 0.5)")
229
+ assert_equal "rgba(0, 0, 255, 0.5)", evaluate("rgba($color: blue, $alpha: 0.5)")
201
230
  end
202
231
 
203
232
  def test_rgba_with_color_tests_types
@@ -214,6 +243,7 @@ class SassFunctionTest < Test::Unit::TestCase
214
243
 
215
244
  def test_red
216
245
  assert_equal("18", evaluate("red(#123456)"))
246
+ assert_equal("18", evaluate("red($color: #123456)"))
217
247
  end
218
248
 
219
249
  def test_red_exception
@@ -222,6 +252,7 @@ class SassFunctionTest < Test::Unit::TestCase
222
252
 
223
253
  def test_green
224
254
  assert_equal("52", evaluate("green(#123456)"))
255
+ assert_equal("52", evaluate("green($color: #123456)"))
225
256
  end
226
257
 
227
258
  def test_green_exception
@@ -230,6 +261,7 @@ class SassFunctionTest < Test::Unit::TestCase
230
261
 
231
262
  def test_blue
232
263
  assert_equal("86", evaluate("blue(#123456)"))
264
+ assert_equal("86", evaluate("blue($color: #123456)"))
233
265
  end
234
266
 
235
267
  def test_blue_exception
@@ -238,6 +270,7 @@ class SassFunctionTest < Test::Unit::TestCase
238
270
 
239
271
  def test_hue
240
272
  assert_equal("18deg", evaluate("hue(hsl(18, 50%, 20%))"))
273
+ assert_equal("18deg", evaluate("hue($color: hsl(18, 50%, 20%))"))
241
274
  end
242
275
 
243
276
  def test_hue_exception
@@ -247,6 +280,7 @@ class SassFunctionTest < Test::Unit::TestCase
247
280
  def test_saturation
248
281
  assert_equal("52%", evaluate("saturation(hsl(20, 52%, 20%))"))
249
282
  assert_equal("52%", evaluate("saturation(hsl(20, 52, 20%))"))
283
+ assert_equal("52%", evaluate("saturation($color: hsl(20, 52, 20%))"))
250
284
  end
251
285
 
252
286
  def test_saturation_exception
@@ -256,6 +290,7 @@ class SassFunctionTest < Test::Unit::TestCase
256
290
  def test_lightness
257
291
  assert_equal("86%", evaluate("lightness(hsl(120, 50%, 86%))"))
258
292
  assert_equal("86%", evaluate("lightness(hsl(120, 50%, 86))"))
293
+ assert_equal("86%", evaluate("lightness($color: hsl(120, 50%, 86))"))
259
294
  end
260
295
 
261
296
  def test_lightness_exception
@@ -266,6 +301,7 @@ class SassFunctionTest < Test::Unit::TestCase
266
301
  assert_equal("1", evaluate("alpha(#123456)"))
267
302
  assert_equal("0.34", evaluate("alpha(rgba(0, 1, 2, 0.34))"))
268
303
  assert_equal("0", evaluate("alpha(hsla(0, 1, 2, 0))"))
304
+ assert_equal("0", evaluate("alpha($color: hsla(0, 1, 2, 0))"))
269
305
  end
270
306
 
271
307
  def test_alpha_exception
@@ -279,6 +315,8 @@ class SassFunctionTest < Test::Unit::TestCase
279
315
  assert_equal("black", evaluate("fade_in(rgba(0, 0, 0, 0.2), 0.8)"))
280
316
  assert_equal("black", evaluate("opacify(rgba(0, 0, 0, 0.2), 1)"))
281
317
  assert_equal("rgba(0, 0, 0, 0.2)", evaluate("opacify(rgba(0, 0, 0, 0.2), 0%)"))
318
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("opacify($color: rgba(0, 0, 0, 0.2), $amount: 0%)"))
319
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("fade-in($color: rgba(0, 0, 0, 0.2), $amount: 0%)"))
282
320
  end
283
321
 
284
322
  def test_opacify_tests_bounds
@@ -300,6 +338,8 @@ class SassFunctionTest < Test::Unit::TestCase
300
338
  assert_equal("rgba(0, 0, 0, 0)", evaluate("fade_out(rgba(0, 0, 0, 0.2), 0.2)"))
301
339
  assert_equal("rgba(0, 0, 0, 0)", evaluate("transparentize(rgba(0, 0, 0, 0.2), 1)"))
302
340
  assert_equal("rgba(0, 0, 0, 0.2)", evaluate("transparentize(rgba(0, 0, 0, 0.2), 0)"))
341
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("transparentize($color: rgba(0, 0, 0, 0.2), $amount: 0)"))
342
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("fade-out($color: rgba(0, 0, 0, 0.2), $amount: 0)"))
303
343
  end
304
344
 
305
345
  def test_transparentize_tests_bounds
@@ -321,6 +361,7 @@ class SassFunctionTest < Test::Unit::TestCase
321
361
  assert_equal("white", evaluate("lighten(#800, 100%)"))
322
362
  assert_equal("#880000", evaluate("lighten(#800, 0%)"))
323
363
  assert_equal("rgba(238, 0, 0, 0.5)", evaluate("lighten(rgba(136, 0, 0, 0.5), 20%)"))
364
+ assert_equal("rgba(238, 0, 0, 0.5)", evaluate("lighten($color: rgba(136, 0, 0, 0.5), $amount: 20%)"))
324
365
  end
325
366
 
326
367
  def test_lighten_tests_bounds
@@ -342,6 +383,7 @@ class SassFunctionTest < Test::Unit::TestCase
342
383
  assert_equal("black", evaluate("darken(#800, 100%)"))
343
384
  assert_equal("#880000", evaluate("darken(#800, 0%)"))
344
385
  assert_equal("rgba(34, 0, 0, 0.5)", evaluate("darken(rgba(136, 0, 0, 0.5), 20%)"))
386
+ assert_equal("rgba(34, 0, 0, 0.5)", evaluate("darken($color: rgba(136, 0, 0, 0.5), $amount: 20%)"))
345
387
  end
346
388
 
347
389
  def test_darken_tests_bounds
@@ -364,6 +406,7 @@ class SassFunctionTest < Test::Unit::TestCase
364
406
  assert_equal("#33ff33", evaluate("saturate(#8a8, 100%)"))
365
407
  assert_equal("#88aa88", evaluate("saturate(#8a8, 0%)"))
366
408
  assert_equal("rgba(158, 63, 63, 0.5)", evaluate("saturate(rgba(136, 85, 85, 0.5), 20%)"))
409
+ assert_equal("rgba(158, 63, 63, 0.5)", evaluate("saturate($color: rgba(136, 85, 85, 0.5), $amount: 20%)"))
367
410
  end
368
411
 
369
412
  def test_saturate_tests_bounds
@@ -386,6 +429,7 @@ class SassFunctionTest < Test::Unit::TestCase
386
429
  assert_equal("#999999", evaluate("desaturate(#8a8, 100%)"))
387
430
  assert_equal("#88aa88", evaluate("desaturate(#8a8, 0%)"))
388
431
  assert_equal("rgba(114, 107, 107, 0.5)", evaluate("desaturate(rgba(136, 85, 85, 0.5), 20%)"))
432
+ assert_equal("rgba(114, 107, 107, 0.5)", evaluate("desaturate($color: rgba(136, 85, 85, 0.5), $amount: 20%)"))
389
433
  end
390
434
 
391
435
  def test_desaturate_tests_bounds
@@ -409,6 +453,7 @@ class SassFunctionTest < Test::Unit::TestCase
409
453
  assert_equal("#88aa88", evaluate("adjust-hue(#8a8, 360deg)"))
410
454
  assert_equal("#88aa88", evaluate("adjust-hue(#8a8, 0deg)"))
411
455
  assert_equal("rgba(136, 106, 17, 0.5)", evaluate("adjust-hue(rgba(136, 17, 17, 0.5), 45deg)"))
456
+ assert_equal("rgba(136, 106, 17, 0.5)", evaluate("adjust-hue($color: rgba(136, 17, 17, 0.5), $degrees: 45deg)"))
412
457
  end
413
458
 
414
459
  def test_adjust_hue_tests_types
@@ -430,6 +475,7 @@ class SassFunctionTest < Test::Unit::TestCase
430
475
  assert_equal("blue", evaluate("mix(transparentize(#f00, 1), #00f, 0%)"))
431
476
  assert_equal("rgba(0, 0, 255, 0)", evaluate("mix(#f00, transparentize(#00f, 1), 0%)"))
432
477
  assert_equal("rgba(255, 0, 0, 0)", evaluate("mix(transparentize(#f00, 1), #00f, 100%)"))
478
+ assert_equal("rgba(255, 0, 0, 0)", evaluate("mix($color-1: transparentize(#f00, 1), $color-2: #00f, $weight: 100%)"))
433
479
  end
434
480
 
435
481
  def test_mix_tests_types
@@ -451,6 +497,7 @@ class SassFunctionTest < Test::Unit::TestCase
451
497
  assert_equal("gray", evaluate("grayscale(#00f)"))
452
498
  assert_equal("white", evaluate("grayscale(white)"))
453
499
  assert_equal("black", evaluate("grayscale(black)"))
500
+ assert_equal("black", evaluate("grayscale($color: black)"))
454
501
  end
455
502
 
456
503
  def tets_grayscale_tests_types
@@ -463,6 +510,7 @@ class SassFunctionTest < Test::Unit::TestCase
463
510
  assert_equal("red", evaluate("complement(aqua)"))
464
511
  assert_equal("white", evaluate("complement(white)"))
465
512
  assert_equal("black", evaluate("complement(black)"))
513
+ assert_equal("black", evaluate("complement($color: black)"))
466
514
  end
467
515
 
468
516
  def tets_complement_tests_types
@@ -481,6 +529,7 @@ class SassFunctionTest < Test::Unit::TestCase
481
529
  def test_unquote
482
530
  assert_equal('foo', evaluate('unquote("foo")'))
483
531
  assert_equal('foo', evaluate('unquote(foo)'))
532
+ assert_equal('foo', evaluate('unquote($string: foo)'))
484
533
  end
485
534
 
486
535
  def test_unquote_tests_type
@@ -490,6 +539,7 @@ class SassFunctionTest < Test::Unit::TestCase
490
539
  def test_quote
491
540
  assert_equal('"foo"', evaluate('quote(foo)'))
492
541
  assert_equal('"foo"', evaluate('quote("foo")'))
542
+ assert_equal('"foo"', evaluate('quote($string: "foo")'))
493
543
  end
494
544
 
495
545
  def test_quote_tests_type
@@ -520,6 +570,7 @@ MSG
520
570
  assert_equal("number", evaluate("type-of(1px)"))
521
571
  assert_equal("bool", evaluate("type-of(true)"))
522
572
  assert_equal("color", evaluate("type-of(#fff)"))
573
+ assert_equal("color", evaluate("type-of($value: #fff)"))
523
574
  end
524
575
 
525
576
  def test_unit
@@ -528,12 +579,14 @@ MSG
528
579
  assert_equal(%Q{"em*px"}, evaluate("unit(10px * 5em)"))
529
580
  assert_equal(%Q{"em*px"}, evaluate("unit(5em * 10px)"))
530
581
  assert_equal(%Q{"em*px/cm*rem"}, evaluate("unit(10px * 5em / 30cm / 1rem)"))
582
+ assert_equal(%Q{"px"}, evaluate("unit($number: 100px)"))
531
583
  assert_error_message("#ff0000 is not a number for `unit'", "unit(#f00)")
532
584
  end
533
585
 
534
586
  def test_unitless
535
587
  assert_equal(%Q{true}, evaluate("unitless(100)"))
536
588
  assert_equal(%Q{false}, evaluate("unitless(100px)"))
589
+ assert_equal(%Q{false}, evaluate("unitless($number: 100px)"))
537
590
  assert_error_message("#ff0000 is not a number for `unitless'", "unitless(#f00)")
538
591
  end
539
592
 
@@ -541,10 +594,44 @@ MSG
541
594
  assert_equal(%Q{true}, evaluate("comparable(2px, 1px)"))
542
595
  assert_equal(%Q{true}, evaluate("comparable(10cm, 3mm)"))
543
596
  assert_equal(%Q{false}, evaluate("comparable(100px, 3em)"))
597
+ assert_equal(%Q{false}, evaluate("comparable($number-1: 100px, $number-2: 3em)"))
544
598
  assert_error_message("#ff0000 is not a number for `comparable'", "comparable(#f00, 1px)")
545
599
  assert_error_message("#ff0000 is not a number for `comparable'", "comparable(1px, #f00)")
546
600
  end
547
601
 
602
+ def test_keyword_args_rgb
603
+ assert_equal(%Q{white}, evaluate("rgb($red: 255, $green: 255, $blue: 255)"))
604
+ end
605
+
606
+ def test_keyword_args_rgba
607
+ assert_equal(%Q{rgba(255, 255, 255, 0.5)}, evaluate("rgba($red: 255, $green: 255, $blue: 255, $alpha: 0.5)"))
608
+ assert_equal(%Q{rgba(255, 255, 255, 0.5)}, evaluate("rgba($color: #fff, $alpha: 0.5)"))
609
+ end
610
+
611
+ def test_keyword_args_rgba_with_extra_args
612
+ assert_equal(%Q{rgba(255, 255, 255, 0.5)}, evaluate("rgba($red: 255, $green: 255, $blue: 255, $alpha: 0.5, $extra: error)"))
613
+ rescue Sass::SyntaxError => e
614
+ assert_equal("Function rgba doesn't take an argument named $extra", e.message)
615
+ end
616
+
617
+ def test_keyword_args_must_have_signature
618
+ evaluate("no-kw-args($fake: value)")
619
+ rescue Sass::SyntaxError => e
620
+ assert_equal("Function no_kw_args doesn't support keyword arguments", e.message)
621
+ end
622
+
623
+ def test_keyword_args_with_missing_argument
624
+ evaluate("rgb($red: 255, $green: 255)")
625
+ rescue Sass::SyntaxError => e
626
+ assert_equal("Function rgb requires an argument named $blue", e.message)
627
+ end
628
+
629
+ def test_only_var_args
630
+ assert_equal "only-var-args(2px, 3px, 4px)", evaluate("only-var-args(1px, 2px, 3px)")
631
+ end
632
+ def test_only_kw_args
633
+ assert_equal "only-kw-args(a, b, c)", evaluate("only-kw-args($a: 1, $b: 2, $c: 3)")
634
+ end
548
635
  private
549
636
 
550
637
  def evaluate(value)