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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)